PR middle-end/78703 - -fprintf-return-value floating point handling incorrect...

PR middle-end/78703 - -fprintf-return-value floating point handling incorrect in locales with a mulltibyte decimal point
	* gimple-ssa-sprintf.c (struct format_result): Remove constant member.
	(struct fmtresult, format_integer, format_floating): Adjust.
	(get_string_length, format_string,format_directive): Same.
	(pass_sprintf_length::compute_format_length): Same.
	(try_substitute_return_value): Simplify slightly.

From-SVN: r244846
This commit is contained in:
Martin Sebor 2017-01-24 01:06:34 +00:00 committed by Martin Sebor
parent 954b452ada
commit a151e93b2b
2 changed files with 88 additions and 110 deletions

View File

@ -1,5 +1,12 @@
2017-01-23 Martin Sebor <msebor@redhat.com> 2017-01-23 Martin Sebor <msebor@redhat.com>
PR middle-end/78703
* gimple-ssa-sprintf.c (struct format_result): Remove constant member.
(struct fmtresult, format_integer, format_floating): Adjust.
(get_string_length, format_string,format_directive): Same.
(pass_sprintf_length::compute_format_length): Same.
(try_substitute_return_value): Simplify slightly.
PR middle-end/78703 PR middle-end/78703
* gimple-ssa-sprintf.c (pass_sprintf_length::gate): Adjust formatting. * gimple-ssa-sprintf.c (pass_sprintf_length::gate): Adjust formatting.
(fmtresult::operator+=): Outlined. (fmtresult::operator+=): Outlined.

View File

@ -183,12 +183,6 @@ struct format_result
the return value of a call. */ the return value of a call. */
bool knownrange; bool knownrange;
/* True when the output of the formatted call is constant (and
thus a candidate for string constant folding). This is rare
and typically requires that the arguments of all directives
are also constant. CONSTANT implies BOUNDED. */
bool constant;
/* True if no individual directive resulted in more than 4095 bytes /* True if no individual directive resulted in more than 4095 bytes
of output (the total NUMBER_CHARS might be greater). */ of output (the total NUMBER_CHARS might be greater). */
bool under4k; bool under4k;
@ -452,7 +446,6 @@ struct fmtresult
: argmin (), argmax (), : argmin (), argmax (),
knownrange (min < HOST_WIDE_INT_MAX), knownrange (min < HOST_WIDE_INT_MAX),
bounded (), bounded (),
constant (),
nullp () nullp ()
{ {
range.min = min; range.min = min;
@ -465,11 +458,10 @@ struct fmtresult
: argmin (), argmax (), : argmin (), argmax (),
knownrange (min < HOST_WIDE_INT_MAX && max < HOST_WIDE_INT_MAX), knownrange (min < HOST_WIDE_INT_MAX && max < HOST_WIDE_INT_MAX),
bounded (), bounded (),
constant (),
nullp () nullp ()
{ {
range.min = min; range.min = min;
range.max = min; range.max = max;
} }
/* The range a directive's argument is in. */ /* The range a directive's argument is in. */
@ -490,12 +482,6 @@ struct fmtresult
false otherwise. */ false otherwise. */
bool bounded; bool bounded;
/* True when the output of a directive is constant. This is rare
and typically requires that the argument(s) of the directive
are also constant (such as determined by constant propagation,
though not value range propagation). */
bool constant;
/* True when the argument is a null pointer. */ /* True when the argument is a null pointer. */
bool nullp; bool nullp;
}; };
@ -818,7 +804,7 @@ static fmtresult
format_none (const directive &, tree) format_none (const directive &, tree)
{ {
fmtresult res (0); fmtresult res (0);
res.bounded = res.constant = true; res.bounded = true;
return res; return res;
} }
@ -828,7 +814,7 @@ static fmtresult
format_percent (const directive &, tree) format_percent (const directive &, tree)
{ {
fmtresult res (1); fmtresult res (1);
res.bounded = res.constant = true; res.bounded = true;
return res; return res;
} }
@ -1095,7 +1081,6 @@ format_integer (const directive &dir, tree arg)
{ {
res.range.max = len; res.range.max = len;
res.bounded = true; res.bounded = true;
res.constant = true;
res.knownrange = true; res.knownrange = true;
res.bounded = true; res.bounded = true;
} }
@ -1537,7 +1522,6 @@ format_floating (const directive &dir, tree arg)
/* The minimum and maximum number of bytes produced by the directive. */ /* The minimum and maximum number of bytes produced by the directive. */
fmtresult res; fmtresult res;
res.constant = true;
/* Get the real type format desription for the target. */ /* Get the real type format desription for the target. */
const REAL_VALUE_TYPE *rvp = TREE_REAL_CST_PTR (arg); const REAL_VALUE_TYPE *rvp = TREE_REAL_CST_PTR (arg);
@ -1634,7 +1618,6 @@ get_string_length (tree str)
fmtresult res; fmtresult res;
res.range.min = res.range.max = tree_to_shwi (slen); res.range.min = res.range.max = tree_to_shwi (slen);
res.bounded = true; res.bounded = true;
res.constant = true;
res.knownrange = true; res.knownrange = true;
return res; return res;
} }
@ -1662,11 +1645,6 @@ get_string_length (tree str)
res.bounded = res.range.max < target_int_max (); res.bounded = res.range.max < target_int_max ();
res.knownrange = res.bounded; res.knownrange = res.bounded;
/* Set RES.CONSTANT to false even though that may be overly
conservative in rare cases like: 'x ? a : b' where a and
b have the same lengths and consist of the same characters. */
res.constant = false;
return res; return res;
} }
@ -1713,7 +1691,6 @@ format_character (const directive &dir, tree arg)
res.range.min = res.range.max = 1; res.range.min = res.range.max = 1;
res.bounded = true; res.bounded = true;
res.knownrange = true; res.knownrange = true;
res.constant = arg && TREE_CODE (arg) == INTEGER_CST;
} }
/* Adjust the lengths for field width. */ /* Adjust the lengths for field width. */
@ -1759,7 +1736,8 @@ format_string (const directive &dir, tree arg)
/* Compute the range the argument's length can be in. */ /* Compute the range the argument's length can be in. */
fmtresult slen = get_string_length (arg); fmtresult slen = get_string_length (arg);
if (slen.constant) if (slen.range.min == slen.range.max
&& slen.range.min < HOST_WIDE_INT_MAX)
{ {
gcc_checking_assert (slen.range.min == slen.range.max); gcc_checking_assert (slen.range.min == slen.range.max);
@ -1807,7 +1785,6 @@ format_string (const directive &dir, tree arg)
precision is either not specified or it is specified and precision is either not specified or it is specified and
its value is known. */ its value is known. */
res.bounded = true; res.bounded = true;
res.constant = dir.prec != HOST_WIDE_INT_MIN;
} }
else if (dir.width == HOST_WIDE_INT_MIN) else if (dir.width == HOST_WIDE_INT_MIN)
{ {
@ -1868,7 +1845,6 @@ format_string (const directive &dir, tree arg)
of bytes is known or contrained to some range. */ of bytes is known or contrained to some range. */
res.bounded = 0 <= dir.prec || slen.bounded; res.bounded = 0 <= dir.prec || slen.bounded;
res.knownrange = slen.knownrange; res.knownrange = slen.knownrange;
res.constant = false;
} }
/* Adjust the lengths for field width. */ /* Adjust the lengths for field width. */
@ -1888,7 +1864,7 @@ format_string (const directive &dir, tree arg)
/* 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 (HOST_WIDE_INT_MIN != dir.width && HOST_WIDE_INT_MIN != dir.prec) if (HOST_WIDE_INT_MIN != dir.width && -1 < dir.prec)
res.knownrange = true; res.knownrange = true;
return res; return res;
@ -1936,10 +1912,9 @@ format_directive (const pass_sprintf_length::call_info &info,
/* Compute the (approximate) length of the formatted output. */ /* Compute the (approximate) length of the formatted output. */
fmtresult fmtres = dir.fmtfunc (dir, arg); fmtresult fmtres = dir.fmtfunc (dir, arg);
/* The overall result is bounded and constant only if the output /* The overall result is bounded only if the output of every directive
of every directive is bounded and constant, respectively. */ is bounded. */
res->bounded &= fmtres.bounded; res->bounded &= fmtres.bounded;
res->constant &= fmtres.constant;
/* Record whether the output of all directives is known to be /* Record whether the output of all directives is known to be
bounded by some maximum, implying that their arguments are bounded by some maximum, implying that their arguments are
@ -2792,11 +2767,10 @@ pass_sprintf_length::compute_format_length (call_info &info,
res->number_chars = res->number_chars_min = res->number_chars_max = 0; res->number_chars = res->number_chars_min = res->number_chars_max = 0;
/* No directive has been seen yet so the length of output is bounded /* No directive has been seen yet so the length of output is bounded
by the known range [0, 0] and constant (with no conversion producing by the known range [0, 0] (with no conversion producing more than
more than 4K bytes) until determined otherwise. */ 4K bytes) until determined otherwise. */
res->bounded = true; res->bounded = true;
res->knownrange = true; res->knownrange = true;
res->constant = true;
res->under4k = true; res->under4k = true;
res->floating = false; res->floating = false;
res->warned = false; res->warned = false;
@ -2877,42 +2851,71 @@ try_substitute_return_value (gimple_stmt_iterator *gsi,
const pass_sprintf_length::call_info &info, const pass_sprintf_length::call_info &info,
const format_result &res) const format_result &res)
{ {
if (!res.bounded)
return false;
tree lhs = gimple_get_lhs (info.callstmt); tree lhs = gimple_get_lhs (info.callstmt);
/* Set to true when the entire call has been removed. */
bool removed = false;
/* The minumum return value. */
unsigned HOST_WIDE_INT minretval = res.number_chars_min;
/* The maximum return value. */
unsigned HOST_WIDE_INT maxretval = res.number_chars_max;
/* Adjust the number of bytes which includes the terminating nul
to reflect the return value of the function which does not.
Because the valid range of the function is [INT_MIN, INT_MAX],
a valid range before the adjustment below is [0, INT_MAX + 1]
(the functions only return negative values on error or undefined
behavior). */
if (minretval <= target_int_max () + 1)
--minretval;
if (maxretval <= target_int_max () + 1)
--maxretval;
/* Avoid the return value optimization when the behavior of the call /* Avoid the return value optimization when the behavior of the call
is undefined either because any directive may have produced 4K or is undefined either because any directive may have produced 4K or
more of output, or the return value exceeds INT_MAX, or because more of output, or the return value exceeds INT_MAX, or because
the output overflows the destination object (but leave it enabled the output overflows the destination object (but leave it enabled
when the function is bounded because then the behavior is well- when the function is bounded because then the behavior is well-
defined). */ defined). */
if (lhs if (res.under4k
&& res.bounded && minretval == maxretval
&& res.under4k && (info.bounded || minretval < info.objsize)
&& (info.bounded || res.number_chars <= info.objsize) && minretval <= target_int_max ()
&& res.number_chars - 1 <= target_int_max ()
/* Not prepared to handle possibly throwing calls here; they shouldn't /* Not prepared to handle possibly throwing calls here; they shouldn't
appear in non-artificial testcases, except when the __*_chk routines appear in non-artificial testcases, except when the __*_chk routines
are badly declared. */ are badly declared. */
&& !stmt_ends_bb_p (info.callstmt)) && !stmt_ends_bb_p (info.callstmt))
{ {
tree cst = build_int_cst (integer_type_node, res.number_chars - 1); tree cst = build_int_cst (integer_type_node, minretval);
if (info.nowrite) if (lhs == NULL_TREE
&& info.nowrite)
{
/* Remove the call to the bounded function with a zero size
(e.g., snprintf(0, 0, "%i", 123)) if there is no lhs. */
unlink_stmt_vdef (info.callstmt);
gsi_remove (gsi, true);
removed = true;
}
else if (info.nowrite)
{ {
/* Replace the call to the bounded function with a zero size /* Replace the call to the bounded function with a zero size
(e.g., snprintf(0, 0, "%i", 123) with the constant result (e.g., snprintf(0, 0, "%i", 123) with the constant result
of the function minus 1 for the terminating NUL which of the function. */
the function's return value does not include. */
if (!update_call_from_tree (gsi, cst)) if (!update_call_from_tree (gsi, cst))
gimplify_and_update_call_from_tree (gsi, cst); gimplify_and_update_call_from_tree (gsi, cst);
gimple *callstmt = gsi_stmt (*gsi); gimple *callstmt = gsi_stmt (*gsi);
update_stmt (callstmt); update_stmt (callstmt);
} }
else else if (lhs)
{ {
/* Replace the left-hand side of the call with the constant /* Replace the left-hand side of the call with the constant
result of the formatted function minus 1 for the terminating result of the formatted function. */
NUL which the function's return value does not include. */
gimple_call_set_lhs (info.callstmt, NULL_TREE); gimple_call_set_lhs (info.callstmt, NULL_TREE);
gimple *g = gimple_build_assign (lhs, cst); gimple *g = gimple_build_assign (lhs, cst);
gsi_insert_after (gsi, g, GSI_NEW_STMT); gsi_insert_after (gsi, g, GSI_NEW_STMT);
@ -2921,88 +2924,56 @@ try_substitute_return_value (gimple_stmt_iterator *gsi,
if (dump_file) if (dump_file)
{ {
location_t callloc = gimple_location (info.callstmt); if (removed)
fprintf (dump_file, "On line %i substituting ", fprintf (dump_file, " Removing call statement.");
LOCATION_LINE (callloc)); else
print_generic_expr (dump_file, cst, dump_flags); {
fprintf (dump_file, " for "); fprintf (dump_file, " Substituting ");
print_generic_expr (dump_file, info.func, dump_flags); print_generic_expr (dump_file, cst, dump_flags);
fprintf (dump_file, " %s (output %s).\n", fprintf (dump_file, " for %s.\n",
info.nowrite ? "call" : "return value", info.nowrite ? "statement" : "return value");
res.constant ? "constant" : "variable"); }
} }
} }
else if (lhs == NULL_TREE else if (lhs)
&& info.nowrite
&& !stmt_ends_bb_p (info.callstmt))
{ {
/* Remove the call to the bounded function with a zero size bool setrange = false;
(e.g., snprintf(0, 0, "%i", 123)) if there is no lhs. */
unlink_stmt_vdef (info.callstmt);
gsi_remove (gsi, true);
if (dump_file)
{
location_t callloc = gimple_location (info.callstmt);
fprintf (dump_file, "On line %i removing ",
LOCATION_LINE (callloc));
print_generic_expr (dump_file, info.func, dump_flags);
fprintf (dump_file, " call.\n");
}
return true;
}
else
{
unsigned HOST_WIDE_INT maxbytes;
if (lhs if ((info.bounded || maxretval < info.objsize)
&& res.bounded && res.under4k
&& ((maxbytes = res.number_chars - 1) <= target_int_max () && (minretval < target_int_max ()
|| (res.number_chars_min - 1 <= target_int_max () && maxretval < target_int_max ()))
&& (maxbytes = res.number_chars_max - 1) <= target_int_max ()))
&& (info.bounded || maxbytes < info.objsize))
{ {
/* If the result is in a valid range bounded by the size of /* If the result is in a valid range bounded by the size of
the destination set it so that it can be used for subsequent the destination set it so that it can be used for subsequent
optimizations. */ optimizations. */
int prec = TYPE_PRECISION (integer_type_node); int prec = TYPE_PRECISION (integer_type_node);
if (res.number_chars < target_int_max () && res.under4k) wide_int min = wi::shwi (minretval, prec);
{ wide_int max = wi::shwi (maxretval, prec);
wide_int num = wi::shwi (res.number_chars - 1, prec); set_range_info (lhs, VR_RANGE, min, max);
set_range_info (lhs, VR_RANGE, num, num);
} setrange = true;
else if (res.number_chars_min < target_int_max ()
&& res.number_chars_max < target_int_max ())
{
wide_int min = wi::shwi (res.under4k ? res.number_chars_min - 1
: target_int_min (), prec);
wide_int max = wi::shwi (res.number_chars_max - 1, prec);
set_range_info (lhs, VR_RANGE, min, max);
}
} }
if (dump_file) if (dump_file)
{ {
const char *inbounds const char *inbounds
= (res.number_chars_min <= info.objsize = (minretval < info.objsize
? (res.number_chars_max <= info.objsize ? (maxretval < info.objsize
? "in" : "potentially out-of") ? "in" : "potentially out-of")
: "out-of"); : "out-of");
location_t callloc = gimple_location (info.callstmt); const char *what = setrange ? "Setting" : "Discarding";
fprintf (dump_file, "On line %i ", LOCATION_LINE (callloc)); if (minretval != maxretval)
print_generic_expr (dump_file, info.func, dump_flags);
const char *ign = lhs ? "" : " ignored";
if (res.number_chars >= HOST_WIDE_INT_MAX)
fprintf (dump_file, fprintf (dump_file,
" %s-bounds return value in range [%lu, %lu]%s.\n", " %s %s-bounds return value range [%llu, %llu].\n",
inbounds, what, inbounds,
(unsigned long)res.number_chars_min - 1, (unsigned long long)minretval,
(unsigned long)res.number_chars_max - 1, ign); (unsigned long long)maxretval);
else else
fprintf (dump_file, " %s-bounds return value %lu%s.\n", fprintf (dump_file, " %s %s-bounds return value %llu.\n",
inbounds, (unsigned long)res.number_chars - 1, ign); what, inbounds, (unsigned long long)minretval);
} }
} }