Commit de6aa933 by Martin Sebor Committed by Martin Sebor

PR middle-end/78521 - [7 Regression] incorrect byte count in -Wformat-length...

PR middle-end/78521 - [7 Regression] incorrect byte count in -Wformat-length warning with non-constant width or precision
PR middle-end/78520 - missing warning for snprintf with size greater than INT_MAX

gcc/ChangeLog:

	PR middle-end/78520
	* gimple-ssa-sprintf.c (target_max_value): Remove.
	(target_int_max, target_size_max): Use TYPE_MAX_VALUE.
	(get_width_and_precision): New function.
	(format_integer, format_floating, get_string_length, format_string):
	Correct handling of width and precision with unknown value.
	(format_directive): Add warning.
	(pass_sprintf_length::compute_format_length): Allow for precision
	to consist of a sole period with no asterisk or digits after it.

gcc/testsuite/ChangeLog:

	PR middle-end/78520
	* gcc.dg/tree-ssa/builtin-sprintf-5.c: Add test cases.
	* gcc.dg/tree-ssa/builtin-sprintf-6.c: New test.
	* gcc.dg/tree-ssa/builtin-sprintf-warn-1.c: Add test cases.
	* gcc.dg/tree-ssa/builtin-sprintf-warn-3.c: Add test cases.

From-SVN: r242935
parent b3a5bff4
2016-11-28 Martin Sebor <msebor@redhat.com>
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.
2016-11-28 Jakub Jelinek <jakub@redhat.com> 2016-11-28 Jakub Jelinek <jakub@redhat.com>
PR rtl-optimization/78546 PR rtl-optimization/78546
...@@ -226,24 +226,10 @@ struct format_result ...@@ -226,24 +226,10 @@ struct format_result
/* Return the value of INT_MIN for the target. */ /* Return the value of INT_MIN for the target. */
static HOST_WIDE_INT static inline HOST_WIDE_INT
target_int_min () target_int_min ()
{ {
const unsigned HOST_WIDE_INT int_min return tree_to_shwi (TYPE_MIN_VALUE (integer_type_node));
= HOST_WIDE_INT_M1U << (TYPE_PRECISION (integer_type_node) - 1);
return int_min;
}
/* Return the largest value for TYPE on the target. */
static unsigned HOST_WIDE_INT
target_max_value (tree type)
{
const unsigned HOST_WIDE_INT max_value
= HOST_WIDE_INT_M1U >> (HOST_BITS_PER_WIDE_INT
- TYPE_PRECISION (type) + 1);
return max_value;
} }
/* Return the value of INT_MAX for the target. */ /* Return the value of INT_MAX for the target. */
...@@ -251,7 +237,7 @@ target_max_value (tree type) ...@@ -251,7 +237,7 @@ target_max_value (tree type)
static inline unsigned HOST_WIDE_INT static inline unsigned HOST_WIDE_INT
target_int_max () 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. */ /* Return the value of SIZE_MAX for the target. */
...@@ -259,7 +245,7 @@ target_int_max () ...@@ -259,7 +245,7 @@ target_int_max ()
static inline unsigned HOST_WIDE_INT static inline unsigned HOST_WIDE_INT
target_size_max () 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 /* Return the constant initial value of DECL if available or DECL
...@@ -845,6 +831,43 @@ format_pointer (const conversion_spec &spec, tree arg) ...@@ -845,6 +831,43 @@ format_pointer (const conversion_spec &spec, tree arg)
return res; return res;
} }
/* Set *PWIDTH and *PPREC according to the width and precision specified
in SPEC. Each is set to HOST_WIDE_INT_MIN when the corresponding
field is specified but unknown, to zero for width and -1 for precision,
respectively when it's not specified, or to a non-negative value
corresponding to the known value. */
static void
get_width_and_precision (const conversion_spec &spec,
HOST_WIDE_INT *pwidth, HOST_WIDE_INT *pprec)
{
HOST_WIDE_INT width = spec.have_width ? spec.width : 0;
HOST_WIDE_INT prec = spec.have_precision ? spec.precision : -1;
if (spec.star_width)
{
if (TREE_CODE (spec.star_width) == INTEGER_CST)
width = abs (tree_to_shwi (spec.star_width));
else
width = HOST_WIDE_INT_MIN;
}
if (spec.star_precision)
{
if (TREE_CODE (spec.star_precision) == INTEGER_CST)
{
prec = tree_to_shwi (spec.star_precision);
if (prec < 0)
prec = 0;
}
else
prec = HOST_WIDE_INT_MIN;
}
*pwidth = width;
*pprec = prec;
}
/* Return a range representing the minimum and maximum number of bytes /* Return a range representing the minimum and maximum number of bytes
that the conversion specification SPEC will write on output for the that the conversion specification SPEC will write on output for the
integer argument ARG when non-null. ARG may be null (for vararg integer argument ARG when non-null. ARG may be null (for vararg
...@@ -855,18 +878,11 @@ format_integer (const conversion_spec &spec, tree arg) ...@@ -855,18 +878,11 @@ format_integer (const conversion_spec &spec, tree arg)
{ {
tree intmax_type_node; tree intmax_type_node;
tree uintmax_type_node; tree uintmax_type_node;
/* Set WIDTH and PRECISION to either the values in the format
specification or to zero. */
int width = spec.have_width ? spec.width : 0;
int prec = spec.have_precision ? spec.precision : 0;
if (spec.star_width) /* Set WIDTH and PRECISION based on the specification. */
width = (TREE_CODE (spec.star_width) == INTEGER_CST HOST_WIDE_INT width;
? tree_to_shwi (spec.star_width) : 0); HOST_WIDE_INT prec;
get_width_and_precision (spec, &width, &prec);
if (spec.star_precision)
prec = (TREE_CODE (spec.star_precision) == INTEGER_CST
? tree_to_shwi (spec.star_precision) : 0);
bool sign = spec.specifier == 'd' || spec.specifier == 'i'; bool sign = spec.specifier == 'd' || spec.specifier == 'i';
...@@ -936,15 +952,8 @@ format_integer (const conversion_spec &spec, tree arg) ...@@ -936,15 +952,8 @@ format_integer (const conversion_spec &spec, tree arg)
} }
else if (TREE_CODE (arg) == INTEGER_CST) 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 /* When a constant argument has been provided use its value
rather than type to determine the length of the output. */ 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. */ /* Base to format the number in. */
int base; int base;
...@@ -977,25 +986,56 @@ format_integer (const conversion_spec &spec, tree arg) ...@@ -977,25 +986,56 @@ format_integer (const conversion_spec &spec, tree arg)
gcc_unreachable (); gcc_unreachable ();
} }
/* Convert the argument to the type of the directive. */ int len;
arg = fold_convert (dirtype, arg);
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 /* True when a conversion is preceded by a prefix indicating the base
of the argument (octal or hexadecimal). */ of the argument (octal or hexadecimal). */
bool maybebase = spec.get_flag ('#'); bool maybebase = spec.get_flag ('#');
int len = tree_digits (arg, base, maybesign, maybebase); len = tree_digits (arg, base, maybesign, maybebase);
if (len < prec) if (len < prec)
len = prec; len = prec;
}
if (len < width) if (len < width)
len = width; len = width;
res.range.max = len; /* The minimum and maximum number of bytes produced by the directive. */
res.range.min = res.range.max; fmtresult res;
res.bounded = true;
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; return res;
} }
...@@ -1106,8 +1146,10 @@ format_integer (const conversion_spec &spec, tree arg) ...@@ -1106,8 +1146,10 @@ format_integer (const conversion_spec &spec, tree arg)
or one whose value range cannot be determined, create a T_MIN or one whose value range cannot be determined, create a T_MIN
constant if the argument's type is signed and T_MAX otherwise, constant if the argument's type is signed and T_MAX otherwise,
and use those to compute the range of bytes that the directive and use those to compute the range of bytes that the directive
can output. */ can output. When precision is specified but unknown, use zero
argmin = build_int_cst (argtype, 1); 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 typeprec = TYPE_PRECISION (dirtype);
int argprec = TYPE_PRECISION (argtype); int argprec = TYPE_PRECISION (argtype);
...@@ -1257,11 +1299,13 @@ format_floating (const conversion_spec &spec, int width, int prec) ...@@ -1257,11 +1299,13 @@ format_floating (const conversion_spec &spec, int width, int prec)
{ {
/* The minimum output is "0x.p+0". */ /* The minimum output is "0x.p+0". */
res.range.min = 6 + (prec > 0 ? prec : 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 /* The output of "%a" is fully specified only when precision
is explicitly specified. */ is explicitly specified and width isn't unknown. */
res.bounded = -1 < prec; res.bounded = INT_MIN != width && -1 < prec;
break; break;
} }
...@@ -1274,13 +1318,16 @@ format_floating (const conversion_spec &spec, int width, int prec) ...@@ -1274,13 +1318,16 @@ format_floating (const conversion_spec &spec, int width, int prec)
res.range.min = (sign res.range.min = (sign
+ 1 /* unit */ + (prec < 0 ? 7 : prec ? prec + 1 : 0) + 1 /* unit */ + (prec < 0 ? 7 : prec ? prec + 1 : 0)
+ 2 /* e+ */ + 2); + 2 /* e+ */ + 2);
/* The maximum output is the minimum plus sign (unless already /* Unless width is uknown the maximum output is the minimum plus
included), plus the difference between the minimum exponent sign (unless already included), plus the difference between
of 2 and the maximum exponent for the type. */ the minimum exponent of 2 and the maximum exponent for the type. */
res.range.max = res.range.min + !sign + logexpdigs - 2; res.range.max = (width == INT_MIN
? HOST_WIDE_INT_M1U
/* "%e" is fully specified and the range of bytes is bounded. */ : res.range.min + !sign + logexpdigs - 2);
res.bounded = true;
/* "%e" is fully specified and the range of bytes is bounded
unless width is unknown. */
res.bounded = INT_MIN != width;
break; break;
} }
...@@ -1296,10 +1343,11 @@ format_floating (const conversion_spec &spec, int width, int prec) ...@@ -1296,10 +1343,11 @@ format_floating (const conversion_spec &spec, int width, int prec)
format_floating_max (double_type_node, 'f'), format_floating_max (double_type_node, 'f'),
format_floating_max (long_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. */ /* "%f" is fully specified and the range of bytes is bounded
res.bounded = true; unless width is unknown. */
res.bounded = INT_MIN != width;
break; break;
} }
case 'G': case 'G':
...@@ -1313,10 +1361,11 @@ format_floating (const conversion_spec &spec, int width, int prec) ...@@ -1313,10 +1361,11 @@ format_floating (const conversion_spec &spec, int width, int prec)
format_floating_max (double_type_node, 'g'), format_floating_max (double_type_node, 'g'),
format_floating_max (long_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. */ /* "%g" is fully specified and the range of bytes is bounded
res.bounded = true; unless width is unknown. */
res.bounded = INT_MIN != width;
break; break;
} }
...@@ -1342,6 +1391,9 @@ format_floating (const conversion_spec &spec, int width, int prec) ...@@ -1342,6 +1391,9 @@ format_floating (const conversion_spec &spec, int width, int prec)
static fmtresult static fmtresult
format_floating (const conversion_spec &spec, tree arg) 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 width = -1;
int prec = -1; int prec = -1;
...@@ -1354,12 +1406,13 @@ format_floating (const conversion_spec &spec, tree arg) ...@@ -1354,12 +1406,13 @@ format_floating (const conversion_spec &spec, tree arg)
else if (spec.star_width) else if (spec.star_width)
{ {
if (TREE_CODE (spec.star_width) == INTEGER_CST) 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; width = tree_to_shwi (spec.star_width);
return res; if (width < 0)
width = -width;
} }
else
width = INT_MIN;
} }
if (spec.have_precision) if (spec.have_precision)
...@@ -1370,6 +1423,7 @@ format_floating (const conversion_spec &spec, tree arg) ...@@ -1370,6 +1423,7 @@ format_floating (const conversion_spec &spec, tree arg)
prec = tree_to_shwi (spec.star_precision); prec = tree_to_shwi (spec.star_precision);
else else
{ {
/* FIXME: Handle non-constant precision. */
res.range.min = res.range.max = HOST_WIDE_INT_M1U; res.range.min = res.range.max = HOST_WIDE_INT_M1U;
return res; return res;
} }
...@@ -1409,9 +1463,9 @@ format_floating (const conversion_spec &spec, tree arg) ...@@ -1409,9 +1463,9 @@ format_floating (const conversion_spec &spec, tree arg)
*pfmt++ = *pf; *pfmt++ = *pf;
/* Append width when specified and precision. */ /* Append width when specified and precision. */
if (width != -1) if (-1 < width)
pfmt += sprintf (pfmt, "%i", width); pfmt += sprintf (pfmt, "%i", width);
if (prec != -1) if (-1 < prec)
pfmt += sprintf (pfmt, ".%i", prec); pfmt += sprintf (pfmt, ".%i", prec);
/* Append the MPFR 'R' floating type specifier (no length modifier /* Append the MPFR 'R' floating type specifier (no length modifier
...@@ -1438,16 +1492,24 @@ format_floating (const conversion_spec &spec, tree arg) ...@@ -1438,16 +1492,24 @@ format_floating (const conversion_spec &spec, tree arg)
*minmax[i] = mpfr_snprintf (NULL, 0, fmtstr, mpfrval); *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 /* The output of all directives except "%a" is fully specified
and so the result is bounded unless it exceeds INT_MAX. and so the result is bounded unless it exceeds INT_MAX.
For "%a" the output is fully specified only when precision For "%a" the output is fully specified only when precision
is explicitly specified. */ is explicitly specified. */
res.bounded = ((TOUPPER (spec.specifier) != 'A' res.bounded = (res.knownrange
|| (0 <= prec && (unsigned) prec < target_int_max ())) && (TOUPPER (spec.specifier) != 'A'
|| (0 <= prec && (unsigned) prec < target_int_max ()))
&& res.range.min < 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; return res;
} }
...@@ -1517,20 +1579,10 @@ get_string_length (tree str) ...@@ -1517,20 +1579,10 @@ get_string_length (tree str)
static fmtresult static fmtresult
format_string (const conversion_spec &spec, tree arg) format_string (const conversion_spec &spec, tree arg)
{ {
unsigned width = spec.have_width && spec.width > 0 ? spec.width : 0; /* Set WIDTH and PRECISION based on the specification. */
int prec = spec.have_precision ? spec.precision : -1; HOST_WIDE_INT width;
HOST_WIDE_INT prec;
if (spec.star_width) get_width_and_precision (spec, &width, &prec);
{
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);
fmtresult res; fmtresult res;
...@@ -1590,11 +1642,12 @@ format_string (const conversion_spec &spec, tree arg) ...@@ -1590,11 +1642,12 @@ format_string (const conversion_spec &spec, tree arg)
res.range = slen.range; res.range = slen.range;
/* The output of "%s" and "%ls" directives with a constant /* The output of "%s" and "%ls" directives with a constant
string is in a known range. For "%s" it is the length string is in a known range unless width of an unknown value
of the string. For "%ls" it is in the range [length, is specified. For "%s" it is the length of the string. For
length * MB_LEN_MAX]. (The final range can be further "%ls" it is in the range [length, length * MB_LEN_MAX].
constrained by width and precision but it's always known.) */ (The final range can be further constrained by width and
res.knownrange = true; precision but it's always known.) */
res.knownrange = -1 < width;
if (spec.modifier == FMT_LEN_l) if (spec.modifier == FMT_LEN_l)
{ {
...@@ -1622,19 +1675,32 @@ format_string (const conversion_spec &spec, tree arg) ...@@ -1622,19 +1675,32 @@ format_string (const conversion_spec &spec, tree arg)
if (0 <= prec) if (0 <= prec)
res.range.max = prec; res.range.max = prec;
} }
else else if (0 <= width)
{ {
/* The output od a "%s" directive with a constant argument /* The output of a "%s" directive with a constant argument
is bounded, constant, and obviously in a known range. */ 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.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.min = prec;
res.range.max = 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 else
{ {
...@@ -1648,10 +1714,10 @@ format_string (const conversion_spec &spec, tree arg) ...@@ -1648,10 +1714,10 @@ format_string (const conversion_spec &spec, tree arg)
{ {
if (slen.range.min >= target_int_max ()) if (slen.range.min >= target_int_max ())
slen.range.min = 0; 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; 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 >= target_int_max ())
slen.range.max = prec; slen.range.max = prec;
} }
...@@ -1674,20 +1740,23 @@ format_string (const conversion_spec &spec, tree arg) ...@@ -1674,20 +1740,23 @@ format_string (const conversion_spec &spec, tree arg)
} }
/* Adjust the lengths for field width. */ /* Adjust the lengths for field width. */
if (res.range.min < width) if (0 < width)
res.range.min = width; {
if (res.range.min < (unsigned HOST_WIDE_INT)width)
res.range.min = width;
if (res.range.max < width) if (res.range.max < (unsigned HOST_WIDE_INT)width)
res.range.max = width; res.range.max = width;
/* Adjust BOUNDED if width happens to make them equal. */ /* Adjust BOUNDED if width happens to make them equal. */
if (res.range.min == res.range.max && res.range.min < target_int_max () if (res.range.min == res.range.max && res.range.min < target_int_max ()
&& bounded) && bounded)
res.bounded = true; res.bounded = true;
}
/* When precision is specified the range of characters on output /* When precision is specified the range of characters on output
is known to be bounded by it. */ is known to be bounded by it. */
if (-1 < prec) if (-1 < width && -1 < prec)
res.knownrange = true; res.knownrange = true;
return res; return res;
...@@ -1803,7 +1872,7 @@ format_directive (const pass_sprintf_length::call_info &info, ...@@ -1803,7 +1872,7 @@ format_directive (const pass_sprintf_length::call_info &info,
(int)cvtlen, cvtbeg, fmtres.range.min, (int)cvtlen, cvtbeg, fmtres.range.min,
navail); navail);
} }
else else if (fmtres.range.max < HOST_WIDE_INT_MAX)
{ {
const char* fmtstr const char* fmtstr
= (info.bounded = (info.bounded
...@@ -1817,6 +1886,19 @@ format_directive (const pass_sprintf_length::call_info &info, ...@@ -1817,6 +1886,19 @@ format_directive (const pass_sprintf_length::call_info &info,
(int)cvtlen, cvtbeg, (int)cvtlen, cvtbeg,
fmtres.range.min, fmtres.range.max, navail); 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 else if (navail < fmtres.range.max
&& (((spec.specifier == 's' && (((spec.specifier == 's'
...@@ -2273,13 +2355,22 @@ pass_sprintf_length::compute_format_length (const call_info &info, ...@@ -2273,13 +2355,22 @@ pass_sprintf_length::compute_format_length (const call_info &info,
if (dollar || !spec.star_width) 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 if (spec.width == 0)
actually a flag. Reset HAVE_WIDTH, set the '0' flag, {
and continue processing other flags. */ /* The '0' that has been interpreted as a width above is
spec.have_width = false; actually a flag. Reset HAVE_WIDTH, set the '0' flag,
spec.set_flag ('0'); 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, /* When either '$' has been seen, or width has not been seen,
the next field is the optional flags followed by an optional the next field is the optional flags followed by an optional
...@@ -2324,6 +2415,7 @@ pass_sprintf_length::compute_format_length (const call_info &info, ...@@ -2324,6 +2415,7 @@ pass_sprintf_length::compute_format_length (const call_info &info,
} }
} }
start_precision:
if ('.' == *pf) if ('.' == *pf)
{ {
++pf; ++pf;
...@@ -2341,7 +2433,12 @@ pass_sprintf_length::compute_format_length (const call_info &info, ...@@ -2341,7 +2433,12 @@ pass_sprintf_length::compute_format_length (const call_info &info,
++pf; ++pf;
} }
else 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) switch (*pf)
...@@ -2701,9 +2798,9 @@ pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator *gsi) ...@@ -2701,9 +2798,9 @@ pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator *gsi)
if (idx_dstsize == HOST_WIDE_INT_M1U) if (idx_dstsize == HOST_WIDE_INT_M1U)
{ {
// For non-bounded functions like sprintf, to determine /* For non-bounded functions like sprintf, determine the size
// the size of the destination from the object or pointer of the destination from the object or pointer passed to it
// passed to it as the first argument. as the first argument. */
dstsize = get_destination_size (gimple_call_arg (info.callstmt, 0)); dstsize = get_destination_size (gimple_call_arg (info.callstmt, 0));
} }
else if (tree size = gimple_call_arg (info.callstmt, idx_dstsize)) else if (tree size = gimple_call_arg (info.callstmt, idx_dstsize))
...@@ -2715,10 +2812,18 @@ pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator *gsi) ...@@ -2715,10 +2812,18 @@ pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator *gsi)
dstsize = tree_to_uhwi (size); dstsize = tree_to_uhwi (size);
/* No object can be larger than SIZE_MAX bytes (half the address /* No object can be larger than SIZE_MAX bytes (half the address
space) on the target. This imposes a limit that's one byte 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) if (dstsize >= target_size_max () / 2)
warning_at (gimple_location (info.callstmt), OPT_Wformat_length_, warning_at (gimple_location (info.callstmt), OPT_Wformat_length_,
"specified destination size %wu too large", "specified destination size %wu is too large",
dstsize);
else if (dstsize > target_int_max ())
warning_at (gimple_location (info.callstmt), OPT_Wformat_length_,
"specified destination size %wu exceeds %<INT_MAX %>",
dstsize); dstsize);
} }
else if (TREE_CODE (size) == SSA_NAME) else if (TREE_CODE (size) == SSA_NAME)
......
2016-11-28 Martin Sebor <msebor@redhat.com>
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.
2016-11-28 Jakub Jelinek <jakub@redhat.com> 2016-11-28 Jakub Jelinek <jakub@redhat.com>
PR c++/72808 PR c++/72808
......
...@@ -44,6 +44,26 @@ void test_arg_int (int i, int n) ...@@ -44,6 +44,26 @@ void test_arg_int (int i, int n)
for (i = -n; i != n; ++i) for (i = -n; i != n; ++i)
T (8, "%08x", 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) void test_arg_string (const char *s)
......
/* 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"} } */
...@@ -233,6 +233,8 @@ void test_sprintf_chk_s_const (void) ...@@ -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); /* { dg-warning "nul past the end" } */
T (-1, "%*s", 1, s0); /* No warning for unknown destination size. */ 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", "123");
T (1, "%.0s", s3); T (1, "%.0s", s3);
T (1, "%.*s", 0, "123"); T (1, "%.*s", 0, "123");
...@@ -450,6 +452,24 @@ void test_sprintf_chk_hh_const (void) ...@@ -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", 10, 1); /* { dg-warning "nul past the end" } */
T (4, "%hhi %hhi", 11, 12); /* { dg-warning "into a region" } */ 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", 0, 7, 0, 9);
T (5, "%0*hhd %0*hhi", 1, 7, 1, 9); T (5, "%0*hhd %0*hhi", 1, 7, 1, 9);
T (5, "%0*hhd %0*hhi", 1, 7, 2, 9); T (5, "%0*hhd %0*hhi", 1, 7, 2, 9);
...@@ -546,14 +566,32 @@ void test_sprintf_chk_h_const (void) ...@@ -546,14 +566,32 @@ void test_sprintf_chk_h_const (void)
T (4, "%#hx", 0x100); /* { dg-warning "into a region" } */ T (4, "%#hx", 0x100); /* { dg-warning "into a region" } */
T (4, "%#hx", -1); /* { 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 #undef MAX
#define MAX 65535 #define MAX 65535
T (1, "%hhu", 0); /* { dg-warning "nul past the end" } */ T (1, "%hu", 0); /* { dg-warning "nul past the end" } */
T (1, "%hhu", 1); /* { dg-warning "nul past the end" } */ T (1, "%hu", 1); /* { dg-warning "nul past the end" } */
T (1, "%hhu", -1); /* { dg-warning "into a region" } */ T (1, "%hu", -1); /* { dg-warning "into a region" } */
T (1, "%hhu", MAX); /* { dg-warning "into a region" } */ T (1, "%hu", MAX); /* { dg-warning "into a region" } */
T (1, "%hhu", MAX + 1); /* { dg-warning "nul past the end" } */ T (1, "%hu", MAX + 1); /* { dg-warning "nul past the end" } */
} }
/* Exercise the "%d", "%i", "%o", "%u", and "%x" directives with /* Exercise the "%d", "%i", "%o", "%u", and "%x" directives with
...@@ -611,6 +649,24 @@ void test_sprintf_chk_integer_const (void) ...@@ -611,6 +649,24 @@ void test_sprintf_chk_integer_const (void)
T ( 8, "%8u", 1); /* { dg-warning "nul past the end" } */ T ( 8, "%8u", 1); /* { dg-warning "nul past the end" } */
T ( 9, "%8u", 1); 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 ( 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%1$i", 1, 23, 456);
T ( 8, "%1$i%2$i%3$i%2$i", 1, 23, 456); /* { dg-warning "nul past the end" } */ 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) ...@@ -691,6 +747,24 @@ void test_sprintf_chk_j_const (void)
T ( 8, "%8ju", I (1)); /* { dg-warning "nul past the end" } */ T ( 8, "%8ju", I (1)); /* { dg-warning "nul past the end" } */
T ( 9, "%8ju", I (1)); 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 /* Exercise the "%ld", "%li", "%lo", "%lu", and "%lx" directives
...@@ -747,6 +821,24 @@ void test_sprintf_chk_l_const (void) ...@@ -747,6 +821,24 @@ void test_sprintf_chk_l_const (void)
T ( 8, "%8lu", 1L); /* { dg-warning "nul past the end" } */ T ( 8, "%8lu", 1L); /* { dg-warning "nul past the end" } */
T ( 9, "%8lu", 1L); 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 /* Exercise the "%lld", "%lli", "%llo", "%llu", and "%llx" directives
...@@ -858,37 +950,56 @@ void test_sprintf_chk_z_const (void) ...@@ -858,37 +950,56 @@ void test_sprintf_chk_z_const (void)
void test_sprintf_chk_a_const (void) void test_sprintf_chk_a_const (void)
{ {
T (-1, "%a", 0.0); T (-1, "%a", 0.0);
T (-1, "%la", 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 /* 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 is missing the number of digits after the decimal point isn't fully
specified by C (it seems like a defect). */ specified by C (it seems like a defect). */
T (0, "%a", 0.0); /* { dg-warning "into a region" } */ T (0, "%a", 0.0); /* { dg-warning "into a region" } */
T (0, "%la", 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 (1, "%a", 0.0); /* { dg-warning "into a region" } */
T (2, "%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 (3, "%a", 0.0); /* { dg-warning "into a region" } */
T (4, "%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 (5, "%a", 0.0); /* { dg-warning "into a region" } */
T (6, "%a", 0.0); /* { dg-warning "writing a terminating nul" } */ T (6, "%a", 0.0); /* { dg-warning "writing a terminating nul" } */
T (7, "%a", 0.0); T (7, "%a", 0.0);
T (0, "%.0a", 0.0); /* { dg-warning "into a region" } */ T (0, "%.a", 0.0); /* { dg-warning "into a region" } */
T (0, "%.0la", 0.0); /* { dg-warning "into a region" } */ T (0, "%.0a", 0.0); /* { dg-warning "into a region" } */
T (1, "%.0a", 0.0); /* { dg-warning "into a region" } */ T (0, "%.0la", 0.0); /* { dg-warning "into a region" } */
T (2, "%.0a", 0.0); /* { dg-warning "into a region" } */ T (1, "%.0a", 0.0); /* { dg-warning "into a region" } */
T (3, "%.0a", 0.0); /* { dg-warning "into a region" } */ T (2, "%.0a", 0.0); /* { dg-warning "into a region" } */
T (4, "%.0a", 0.0); /* { dg-warning "into a region" } */ T (3, "%.0a", 0.0); /* { dg-warning "into a region" } */
T (5, "%.0a", 0.0); /* { dg-warning "into a region" } */ T (4, "%.0a", 0.0); /* { dg-warning "into a region" } */
T (6, "%.0a", 0.0); /* { dg-warning "writing a terminating nul" } */ 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); T (7, "%.0a", 0.0);
} }
void test_sprintf_chk_e_const (void) void test_sprintf_chk_e_const (void)
{ {
T (-1, "%E", 0.0); T (-1, "%E", 0.0);
T (-1, "%lE", 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" } */
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) ...@@ -910,8 +1021,10 @@ void test_sprintf_chk_e_const (void)
T (16, "%.8e", -1.9e+104); /* { dg-warning "nul past the end" } */ T (16, "%.8e", -1.9e+104); /* { dg-warning "nul past the end" } */
T (17, "%.8e", -2.0e+105); /* -2.00000000e+105 */ 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", 0.0); /* { dg-warning "nul past the end" } */
T ( 5, "%.0e", 1.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); T ( 6, "%.0e", 1.0);
/* The actual output of the following directives depends on the rounding /* The actual output of the following directives depends on the rounding
...@@ -938,7 +1051,7 @@ void test_sprintf_chk_e_const (void) ...@@ -938,7 +1051,7 @@ void test_sprintf_chk_e_const (void)
the value one, and unknown strings are assumed to have a zero the value one, and unknown strings are assumed to have a zero
length. */ 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 (-1, "%s", s);
T ( 0, "%s", s); /* { dg-warning "nul past the end" } */ 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) ...@@ -946,6 +1059,19 @@ void test_sprintf_chk_s_nonconst (int i, const char *s)
T ( 1, "%.0s", s); T ( 1, "%.0s", s);
T ( 1, "%.1s", s); /* { dg-warning "nul past the end" } */ 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, /* The following will definitely write past the end of the buffer,
but since at level 1 the length of an unknown string argument but since at level 1 the length of an unknown string argument
is assumed to be zero, it will write the terminating nul past 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) ...@@ -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 /* Exercise the hh length modifier with all integer specifiers and
a non-constant argument. */ 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); T (-1, "%hhd", a);
...@@ -999,11 +1125,48 @@ void test_sprintf_chk_hh_nonconst (int 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, "%#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 (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, "%2hhd", a);
T (3, "%2hhi", a); T (3, "%2hhi", a);
T (3, "%2hho", a); T (3, "%2hho", a);
T (3, "%2hhu", a); T (3, "%2hhu", a);
T (3, "%2hhx", 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 /* Exercise cases where the type of the actual argument (whose value
and range are unknown) constrain the size of the output and so and range are unknown) constrain the size of the output and so
...@@ -1012,6 +1175,55 @@ void test_sprintf_chk_hh_nonconst (int a) ...@@ -1012,6 +1175,55 @@ void test_sprintf_chk_hh_nonconst (int a)
T (2, "%hhd", (UChar)a); T (2, "%hhd", (UChar)a);
T (2, "%hhi", (UChar)a); T (2, "%hhi", (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 /* Exercise the h length modifier with all integer specifiers and
...@@ -1063,7 +1275,7 @@ void test_sprintf_chk_h_nonconst (int a) ...@@ -1063,7 +1275,7 @@ void test_sprintf_chk_h_nonconst (int a)
/* Exercise all integer specifiers with no modifier and a non-constant /* Exercise all integer specifiers with no modifier and a non-constant
argument. */ 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); T (-1, "%d", a);
...@@ -1104,12 +1316,22 @@ void test_sprintf_chk_int_nonconst (int a) ...@@ -1104,12 +1316,22 @@ void test_sprintf_chk_int_nonconst (int a)
T (3, "%2o", a); T (3, "%2o", a);
T (3, "%2u", a); T (3, "%2u", a);
T (3, "%2x", 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, "%E", d);
T (-1, "%lE", 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 "writing between 12 and 14 bytes into a region of size 0" } */
T ( 0, "%e", d); /* { dg-warning "into a region" } */ T ( 0, "%e", d); /* { dg-warning "into a region" } */
...@@ -1123,9 +1345,9 @@ void test_sprintf_chk_e_nonconst (double d) ...@@ -1123,9 +1345,9 @@ void test_sprintf_chk_e_nonconst (double d)
T (14, "%E", d); T (14, "%E", 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 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 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" } */
/* The range of output of "%.0e" is between 5 and 7 bytes (not counting /* The range of output of "%.0e" is between 5 and 7 bytes (not counting
the terminating NUL. */ the terminating NUL. */
...@@ -1136,6 +1358,9 @@ void test_sprintf_chk_e_nonconst (double d) ...@@ -1136,6 +1358,9 @@ void test_sprintf_chk_e_nonconst (double d)
the terminating NUL. */ the terminating NUL. */
T ( 7, "%.1e", d); /* { dg-warning "writing a terminating nul past the end" } */ T ( 7, "%.1e", d); /* { dg-warning "writing a terminating nul past the end" } */
T ( 8, "%.1e", d); 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) void test_sprintf_chk_f_nonconst (double d)
...@@ -1204,7 +1429,6 @@ void test_vsprintf_chk_c (__builtin_va_list va) ...@@ -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, /* Here in the best case each argument will format as single character,
causing the terminating NUL to be written past the end. */ causing the terminating NUL to be written past the end. */
T (3, "%lc%c%c"); T (3, "%lc%c%c");
} }
void test_vsprintf_chk_int (__builtin_va_list va) void test_vsprintf_chk_int (__builtin_va_list va)
...@@ -1254,9 +1478,11 @@ 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, ...) \ #define T(size, fmt, ...) \
__builtin_snprintf (buffer (size), objsize (size), fmt, __VA_ARGS__) __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 /* Verify the full text of the diagnostic for just the distinct messages
and use abbreviations in subsequent test cases. */ and use abbreviations in subsequent test cases. */
...@@ -1306,7 +1532,7 @@ void test_snprintf_chk_c_const (void) ...@@ -1306,7 +1532,7 @@ void test_snprintf_chk_c_const (void)
the function by __builtin_object_size) is diagnosed. */ 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" } */ __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", 0);
T (0, "%c%c", 0, 0); T (0, "%c%c", 0, 0);
...@@ -1417,7 +1643,7 @@ void test_vsprintf_int (__builtin_va_list va) ...@@ -1417,7 +1643,7 @@ void test_vsprintf_int (__builtin_va_list va)
void test_vsnprintf_s (__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 (0, "%s");
T (1, "%s"); T (1, "%s");
...@@ -1442,7 +1668,7 @@ void test_vsnprintf_chk_s (__builtin_va_list va) ...@@ -1442,7 +1668,7 @@ void test_vsnprintf_chk_s (__builtin_va_list va)
the function by __builtin_object_size) is diagnosed. */ 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, 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 (0, "%s");
T (1, "%s"); T (1, "%s");
......
/* { dg-do compile } */ /* { dg-do compile } */
/* { dg-options "-std=c99 -O2 -Wformat -Wformat-length=1 -ftrack-macro-expansion=0" } */ /* { dg-options "-std=c99 -O2 -Wformat -Wformat-length=1 -ftrack-macro-expansion=0" } */
typedef __SIZE_TYPE__ size_t;
#ifndef LINE #ifndef LINE
# define LINE 0 # define LINE 0
#endif #endif
...@@ -232,3 +234,48 @@ void test_sprintf_chk_range_sshort (signed short *a, signed short *b) ...@@ -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 (998, 999));
T ( 4, "%i", Ra (999, 1000)); /* { dg-warning "may write a terminating nul past the end of the destination" } */ 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" } */
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment