Commit 954b452a by Martin Sebor Committed by Martin Sebor

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 (pass_sprintf_length::gate): Adjust formatting.
	(fmtresult::operator+=): Outlined.
	(struct fmtresult): Add ctors.
	(struct conversion_spec): Rename...
	(struct directive): ...to this.  Add and remove data members.
	(directive::set_width, directive::set_precision): New functions.
	(format_percent): Use fmtresult ctor.
	(get_width_and_precision): Remove.
	(format_integer): Make naming changes.  Avoid computing width and
	precision.
	(format_floating): Same.  Adjust indentation.
	(format_character, format_none): New functions.
	(format_string): Moved character handling to format_character.
	(format_directive): Remove arguments, change return type.
	(parse_directive): New function.
	(pass_sprintf_length::compute_format_length): Move directive
	parsing to parse_directive.

From-SVN: r244845
parent 86b2a558
2017-01-23 Martin Sebor <msebor@redhat.com>
PR middle-end/78703
* gimple-ssa-sprintf.c (pass_sprintf_length::gate): Adjust formatting.
(fmtresult::operator+=): Outlined.
(struct fmtresult): Add ctors.
(struct conversion_spec): Rename...
(struct directive): ...to this. Add and remove data members.
(directive::set_width, directive::set_precision): New functions.
(format_percent): Use fmtresult ctor.
(get_width_and_precision): Remove.
(format_integer): Make naming changes. Avoid computing width and
precision.
(format_floating): Same. Adjust indentation.
(format_character, format_none): New functions.
(format_string): Moved character handling to format_character.
(format_directive): Remove arguments, change return type.
(parse_directive): New function.
(pass_sprintf_length::compute_format_length): Move directive
parsing to parse_directive.
2017-01-23 Jakub Jelinek <jakub@redhat.com> 2017-01-23 Jakub Jelinek <jakub@redhat.com>
* tree.h (assign_assembler_name_if_neeeded): Rename to ... * tree.h (assign_assembler_name_if_neeeded): Rename to ...
......
...@@ -82,7 +82,7 @@ along with GCC; see the file COPYING3. If not see ...@@ -82,7 +82,7 @@ along with GCC; see the file COPYING3. If not see
/* The likely worst case value of MB_LEN_MAX for the target, large enough /* The likely worst case value of MB_LEN_MAX for the target, large enough
for UTF-8. Ideally, this would be obtained by a target hook if it were for UTF-8. Ideally, this would be obtained by a target hook if it were
to be used for optimization but it's good enough as is for warnings. */ to be used for optimization but it's good enough as is for warnings. */
#define target_mb_len_max 6 #define target_mb_len_max() 6
/* The maximum number of bytes a single non-string directive can result /* The maximum number of bytes a single non-string directive can result
in. This is the result of printf("%.*Lf", INT_MAX, -LDBL_MAX) for in. This is the result of printf("%.*Lf", INT_MAX, -LDBL_MAX) for
...@@ -141,7 +141,9 @@ pass_sprintf_length::gate (function *) ...@@ -141,7 +141,9 @@ pass_sprintf_length::gate (function *)
not optimizing and the pass is being invoked early, or when not optimizing and the pass is being invoked early, or when
optimizing and the pass is being invoked during optimization optimizing and the pass is being invoked during optimization
(i.e., "late"). */ (i.e., "late"). */
return ((warn_format_overflow > 0 || flag_printf_return_value) return ((warn_format_overflow > 0
|| warn_format_trunc > 0
|| flag_printf_return_value)
&& (optimize > 0) == fold_return_value); && (optimize > 0) == fold_return_value);
} }
...@@ -215,20 +217,26 @@ struct format_result ...@@ -215,20 +217,26 @@ struct format_result
} }
/* Increment the number of output characters by N. */ /* Increment the number of output characters by N. */
format_result& operator+= (unsigned HOST_WIDE_INT n) format_result& operator+= (unsigned HOST_WIDE_INT);
{
gcc_assert (n < HOST_WIDE_INT_MAX);
if (number_chars < HOST_WIDE_INT_MAX)
number_chars += n;
if (number_chars_min < HOST_WIDE_INT_MAX)
number_chars_min += n;
if (number_chars_max < HOST_WIDE_INT_MAX)
number_chars_max += n;
return *this;
}
}; };
format_result&
format_result::operator+= (unsigned HOST_WIDE_INT n)
{
gcc_assert (n < HOST_WIDE_INT_MAX);
if (number_chars < HOST_WIDE_INT_MAX)
number_chars += n;
if (number_chars_min < HOST_WIDE_INT_MAX)
number_chars_min += n;
if (number_chars_max < HOST_WIDE_INT_MAX)
number_chars_max += n;
return *this;
}
/* Return the value of INT_MIN for the target. */ /* Return the value of INT_MIN for the target. */
static inline HOST_WIDE_INT static inline HOST_WIDE_INT
...@@ -438,10 +446,30 @@ struct result_range ...@@ -438,10 +446,30 @@ struct result_range
struct fmtresult struct fmtresult
{ {
fmtresult () /* Construct a FMTRESULT object with all counters initialized
: argmin (), argmax (), knownrange (), bounded (), constant (), nullp () to MIN. KNOWNRANGE is set when MIN is valid. */
fmtresult (unsigned HOST_WIDE_INT min = HOST_WIDE_INT_MAX)
: argmin (), argmax (),
knownrange (min < HOST_WIDE_INT_MAX),
bounded (),
constant (),
nullp ()
{ {
range.min = range.max = HOST_WIDE_INT_MAX; range.min = min;
range.max = min;
}
/* Construct a FMTRESULT object with all counters initialized
to MIN. KNOWNRANGE is set when MIN is valid. */
fmtresult (unsigned HOST_WIDE_INT min, unsigned HOST_WIDE_INT max)
: argmin (), argmax (),
knownrange (min < HOST_WIDE_INT_MAX && max < HOST_WIDE_INT_MAX),
bounded (),
constant (),
nullp ()
{
range.min = min;
range.max = min;
} }
/* The range a directive's argument is in. */ /* The range a directive's argument is in. */
...@@ -474,21 +502,22 @@ struct fmtresult ...@@ -474,21 +502,22 @@ struct fmtresult
/* Description of a conversion specification. */ /* Description of a conversion specification. */
struct conversion_spec struct directive
{ {
/* The 1-based directive number (for debugging). */
unsigned dirno;
/* The first character of the directive and its length. */
const char *beg;
size_t len;
/* A bitmap of flags, one for each character. */ /* A bitmap of flags, one for each character. */
unsigned flags[256 / sizeof (int)]; unsigned flags[256 / sizeof (int)];
/* Numeric width as in "%8x". */
int width;
/* Numeric precision as in "%.32s". */
int precision;
/* Width specified via the '*' character. Need not be INTEGER_CST. /* The specified width, or -1 if not specified. */
For vararg functions set to void_node. */ HOST_WIDE_INT width;
tree star_width; /* The specified precision, or -1 if not specified. */
/* Precision specified via the asterisk. Need not be INTEGER_CST. HOST_WIDE_INT prec;
For vararg functions set to void_node. */
tree star_precision;
/* Length modifier. */ /* Length modifier. */
format_lengths modifier; format_lengths modifier;
...@@ -496,18 +525,13 @@ struct conversion_spec ...@@ -496,18 +525,13 @@ struct conversion_spec
/* Format specifier character. */ /* Format specifier character. */
char specifier; char specifier;
/* Numeric width was given. */ /* The argument of the directive or null when the directive doesn't
unsigned have_width: 1; take one or when none is available (such as for vararg functions). */
/* Numeric precision was given. */ tree arg;
unsigned have_precision: 1;
/* Non-zero when certain flags should be interpreted even for a directive
that normally doesn't accept them (used when "%p" with flags such as
space or plus is interepreted as a "%x". */
unsigned force_flags: 1;
/* Format conversion function that given a conversion specification /* Format conversion function that given a conversion specification
and an argument returns the formatting result. */ and an argument returns the formatting result. */
fmtresult (*fmtfunc) (const conversion_spec &, tree); fmtresult (*fmtfunc) (const directive &, tree);
/* Return True when a the format flag CHR has been used. */ /* Return True when a the format flag CHR has been used. */
bool get_flag (char chr) const bool get_flag (char chr) const
...@@ -532,6 +556,56 @@ struct conversion_spec ...@@ -532,6 +556,56 @@ struct conversion_spec
flags[c / (CHAR_BIT * sizeof *flags)] flags[c / (CHAR_BIT * sizeof *flags)]
&= ~(1U << (c % (CHAR_BIT * sizeof *flags))); &= ~(1U << (c % (CHAR_BIT * sizeof *flags)));
} }
/* Set the width to VAL. */
void set_width (HOST_WIDE_INT val)
{
width = val;
}
/* Set the width to ARG. */
void set_width (tree arg)
{
if (tree_fits_shwi_p (arg))
{
width = tree_to_shwi (arg);
if (width < 0)
{
if (width == HOST_WIDE_INT_MIN)
{
/* Avoid undefined behavior due to negating a minimum.
This case will be diagnosed since it will result in
more than INT_MAX bytes on output, either by the
directive itself (when INT_MAX < HOST_WIDE_INT_MAX)
or by the format function itself. */
width = HOST_WIDE_INT_MAX;
}
else
width = -width;
}
}
else
width = HOST_WIDE_INT_MIN;
}
/* Set the precision to val. */
void set_precision (HOST_WIDE_INT val)
{
prec = val;
}
/* Set the precision to ARG. */
void set_precision (tree arg)
{
if (tree_fits_shwi_p (arg))
{
prec = tree_to_shwi (arg);
if (prec < 0)
prec = -1;
}
else
prec = HOST_WIDE_INT_MIN;
}
}; };
/* Return the logarithm of X in BASE. */ /* Return the logarithm of X in BASE. */
...@@ -738,14 +812,22 @@ struct pass_sprintf_length::call_info ...@@ -738,14 +812,22 @@ struct pass_sprintf_length::call_info
} }
}; };
/* Return the result of formatting a no-op directive (such as '%n'). */
static fmtresult
format_none (const directive &, tree)
{
fmtresult res (0);
res.bounded = res.constant = true;
return res;
}
/* Return the result of formatting the '%%' directive. */ /* Return the result of formatting the '%%' directive. */
static fmtresult static fmtresult
format_percent (const conversion_spec &, tree) format_percent (const directive &, tree)
{ {
fmtresult res; fmtresult res (1);
res.argmin = res.argmax = NULL_TREE;
res.range.min = res.range.max = 1;
res.bounded = res.constant = true; res.bounded = res.constant = true;
return res; return res;
} }
...@@ -791,59 +873,6 @@ build_intmax_type_nodes (tree *pintmax, tree *puintmax) ...@@ -791,59 +873,6 @@ build_intmax_type_nodes (tree *pintmax, tree *puintmax)
} }
} }
/* 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 = tree_to_shwi (spec.star_width);
if (width < 0)
{
if (width == HOST_WIDE_INT_MIN)
{
/* Avoid undefined behavior due to negating a minimum.
This case will be diagnosed since it will result in
more than INT_MAX bytes on output, either by the
directive itself (when INT_MAX < HOST_WIDE_INT_MAX)
or by the format function itself. */
width = HOST_WIDE_INT_MAX;
}
else
width = -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 = -1;
}
else
prec = HOST_WIDE_INT_MIN;
}
*pwidth = width;
*pprec = prec;
}
/* With the range [*ARGMIN, *ARGMAX] of an integer directive's actual /* With the range [*ARGMIN, *ARGMAX] of an integer directive's actual
argument, due to the conversion from either *ARGMIN or *ARGMAX to argument, due to the conversion from either *ARGMIN or *ARGMAX to
the type of the directive's formal argument it's possible for both the type of the directive's formal argument it's possible for both
...@@ -908,32 +937,31 @@ adjust_range_for_overflow (tree dirtype, tree *argmin, tree *argmax) ...@@ -908,32 +937,31 @@ adjust_range_for_overflow (tree dirtype, tree *argmin, tree *argmax)
} }
/* 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 DIR 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
functions). */ functions). */
static fmtresult static fmtresult
format_integer (const conversion_spec &spec, tree arg) format_integer (const directive &dir, tree arg)
{ {
tree intmax_type_node; tree intmax_type_node;
tree uintmax_type_node; tree uintmax_type_node;
/* Set WIDTH and PRECISION based on the specification. */ /* Set WIDTH and PRECISION based on the specification. */
HOST_WIDE_INT width; HOST_WIDE_INT width = dir.width;
HOST_WIDE_INT prec; HOST_WIDE_INT prec = dir.prec;
get_width_and_precision (spec, &width, &prec);
bool sign = spec.specifier == 'd' || spec.specifier == 'i'; bool sign = dir.specifier == 'd' || dir.specifier == 'i';
/* The type of the "formal" argument expected by the directive. */ /* The type of the "formal" argument expected by the directive. */
tree dirtype = NULL_TREE; tree dirtype = NULL_TREE;
/* Determine the expected type of the argument from the length /* Determine the expected type of the argument from the length
modifier. */ modifier. */
switch (spec.modifier) switch (dir.modifier)
{ {
case FMT_LEN_none: case FMT_LEN_none:
if (spec.specifier == 'p') if (dir.specifier == 'p')
dirtype = ptr_type_node; dirtype = ptr_type_node;
else else
dirtype = sign ? integer_type_node : unsigned_type_node; dirtype = sign ? integer_type_node : unsigned_type_node;
...@@ -1000,12 +1028,12 @@ format_integer (const conversion_spec &spec, tree arg) ...@@ -1000,12 +1028,12 @@ format_integer (const conversion_spec &spec, tree arg)
/* True when a signed conversion is preceded by a sign or space. */ /* True when a signed conversion is preceded by a sign or space. */
bool maybesign = false; bool maybesign = false;
switch (spec.specifier) switch (dir.specifier)
{ {
case 'd': case 'd':
case 'i': case 'i':
/* Space and '+' are only meaningful for signed conversions. */ /* Space and '+' are only meaningful for signed conversions. */
maybesign = spec.get_flag (' ') | spec.get_flag ('+'); maybesign = dir.get_flag (' ') | dir.get_flag ('+');
base = 10; base = 10;
break; break;
case 'u': case 'u':
...@@ -1033,7 +1061,7 @@ format_integer (const conversion_spec &spec, tree arg) ...@@ -1033,7 +1061,7 @@ format_integer (const conversion_spec &spec, tree arg)
when it results in just one byte (with width having the normal when it results in just one byte (with width having the normal
effect). This must extend to the case of a specified precision effect). This must extend to the case of a specified precision
with an unknown value because it can be zero. */ with an unknown value because it can be zero. */
len = ((base == 8 && spec.get_flag ('#')) || maybesign); len = ((base == 8 && dir.get_flag ('#')) || maybesign);
} }
else else
{ {
...@@ -1042,7 +1070,7 @@ format_integer (const conversion_spec &spec, tree arg) ...@@ -1042,7 +1070,7 @@ format_integer (const conversion_spec &spec, tree arg)
/* 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 = dir.get_flag ('#');
len = tree_digits (arg, base, prec, maybesign, maybebase); len = tree_digits (arg, base, prec, maybesign, maybebase);
if (len < 1) if (len < 1)
len = HOST_WIDE_INT_MAX; len = HOST_WIDE_INT_MAX;
...@@ -1137,7 +1165,7 @@ format_integer (const conversion_spec &spec, tree arg) ...@@ -1137,7 +1165,7 @@ format_integer (const conversion_spec &spec, tree arg)
if (code == INTEGER_CST) if (code == INTEGER_CST)
{ {
arg = gimple_assign_rhs1 (def); arg = gimple_assign_rhs1 (def);
return format_integer (spec, arg); return format_integer (dir, arg);
} }
if (code == NOP_EXPR) if (code == NOP_EXPR)
...@@ -1215,8 +1243,8 @@ format_integer (const conversion_spec &spec, tree arg) ...@@ -1215,8 +1243,8 @@ format_integer (const conversion_spec &spec, tree arg)
/* For unsigned conversions/directives, use the minimum (i.e., 0 /* For unsigned conversions/directives, use the minimum (i.e., 0
or 1) and maximum to compute the shortest and longest output, or 1) and maximum to compute the shortest and longest output,
respectively. */ respectively. */
res.range.min = format_integer (spec, argmin).range.min; res.range.min = format_integer (dir, argmin).range.min;
res.range.max = format_integer (spec, argmax).range.max; res.range.max = format_integer (dir, argmax).range.max;
} }
else else
{ {
...@@ -1225,8 +1253,8 @@ format_integer (const conversion_spec &spec, tree arg) ...@@ -1225,8 +1253,8 @@ format_integer (const conversion_spec &spec, tree arg)
to compute the longest output. This is important when precision to compute the longest output. This is important when precision
is specified but unknown because otherwise both output lengths is specified but unknown because otherwise both output lengths
would reflect the largest possible precision (i.e., INT_MAX). */ would reflect the largest possible precision (i.e., INT_MAX). */
res.range.min = format_integer (spec, argmax).range.min; res.range.min = format_integer (dir, argmax).range.min;
res.range.max = format_integer (spec, argmin).range.max; res.range.max = format_integer (dir, argmin).range.max;
} }
/* The result is bounded either when the argument is determined to be /* The result is bounded either when the argument is determined to be
...@@ -1336,11 +1364,7 @@ format_floating_max (tree type, char spec, HOST_WIDE_INT prec) ...@@ -1336,11 +1364,7 @@ format_floating_max (tree type, char spec, HOST_WIDE_INT prec)
const real_format *rfmt = REAL_MODE_FORMAT (mode); const real_format *rfmt = REAL_MODE_FORMAT (mode);
REAL_VALUE_TYPE rv; REAL_VALUE_TYPE rv;
{ real_maxval (&rv, 0, mode);
char buf[256];
get_max_float (rfmt, buf, sizeof buf);
real_from_string (&rv, buf);
}
/* Convert the GCC real value representation with the precision /* Convert the GCC real value representation with the precision
of the real type to the mpfr_t format with the GCC default of the real type to the mpfr_t format with the GCC default
...@@ -1354,17 +1378,16 @@ format_floating_max (tree type, char spec, HOST_WIDE_INT prec) ...@@ -1354,17 +1378,16 @@ format_floating_max (tree type, char spec, HOST_WIDE_INT 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 output for any argument that the conversion specification DIR will output for any argument
given the WIDTH and PRECISION (extracted from SPEC). This function given the WIDTH and PRECISION (extracted from DIR). This function
is used when the directive argument or its value isn't known. */ is used when the directive argument or its value isn't known. */
static fmtresult static fmtresult
format_floating (const conversion_spec &spec, HOST_WIDE_INT width, format_floating (const directive &dir)
HOST_WIDE_INT prec)
{ {
tree type; tree type;
switch (spec.modifier) switch (dir.modifier)
{ {
case FMT_LEN_l: case FMT_LEN_l:
case FMT_LEN_none: case FMT_LEN_none:
...@@ -1391,10 +1414,10 @@ format_floating (const conversion_spec &spec, HOST_WIDE_INT width, ...@@ -1391,10 +1414,10 @@ format_floating (const conversion_spec &spec, HOST_WIDE_INT width,
/* The minimum output as determined by flags. It's always at least 1. */ /* The minimum output as determined by flags. It's always at least 1. */
int flagmin = (1 /* for the first digit */ int flagmin = (1 /* for the first digit */
+ (spec.get_flag ('+') | spec.get_flag (' ')) + (dir.get_flag ('+') | dir.get_flag (' '))
+ (prec == 0 && spec.get_flag ('#'))); + (dir.prec == 0 && dir.get_flag ('#')));
if (width == INT_MIN || prec == INT_MIN) if (dir.width == HOST_WIDE_INT_MIN || dir.prec == HOST_WIDE_INT_MIN)
{ {
/* When either width or precision is specified but unknown /* When either width or precision is specified but unknown
the upper bound is the maximum. Otherwise it will be the upper bound is the maximum. Otherwise it will be
...@@ -1404,16 +1427,16 @@ format_floating (const conversion_spec &spec, HOST_WIDE_INT width, ...@@ -1404,16 +1427,16 @@ format_floating (const conversion_spec &spec, HOST_WIDE_INT width,
else else
res.range.max = HOST_WIDE_INT_M1U; res.range.max = HOST_WIDE_INT_M1U;
switch (spec.specifier) switch (dir.specifier)
{ {
case 'A': case 'A':
case 'a': case 'a':
{ {
res.range.min = flagmin + 5 + (prec > 0 ? prec + 1 : 0); res.range.min = flagmin + 5 + (dir.prec > 0 ? dir.prec + 1 : 0);
if (res.range.max == HOST_WIDE_INT_M1U) if (res.range.max == HOST_WIDE_INT_M1U)
{ {
/* Compute the upper bound for -TYPE_MAX. */ /* Compute the upper bound for -TYPE_MAX. */
res.range.max = format_floating_max (type, 'a', prec); res.range.max = format_floating_max (type, 'a', dir.prec);
} }
break; break;
...@@ -1425,8 +1448,8 @@ format_floating (const conversion_spec &spec, HOST_WIDE_INT width, ...@@ -1425,8 +1448,8 @@ format_floating (const conversion_spec &spec, HOST_WIDE_INT width,
/* The minimum output is "[-+]1.234567e+00" regardless /* The minimum output is "[-+]1.234567e+00" regardless
of the value of the actual argument. */ of the value of the actual argument. */
res.range.min = (flagmin res.range.min = (flagmin
+ (prec == INT_MIN + (dir.prec == HOST_WIDE_INT_MIN
? 0 : prec < 0 ? 7 : prec ? prec + 1 : 0) ? 0 : dir.prec < 0 ? 7 : dir.prec ? dir.prec + 1 : 0)
+ 2 /* e+ */ + 2); + 2 /* e+ */ + 2);
if (res.range.max == HOST_WIDE_INT_M1U) if (res.range.max == HOST_WIDE_INT_M1U)
...@@ -1434,7 +1457,7 @@ format_floating (const conversion_spec &spec, HOST_WIDE_INT width, ...@@ -1434,7 +1457,7 @@ format_floating (const conversion_spec &spec, HOST_WIDE_INT width,
/* MPFR uses a precision of 16 by default for some reason. /* MPFR uses a precision of 16 by default for some reason.
Set it to the C default of 6. */ Set it to the C default of 6. */
res.range.max = format_floating_max (type, 'e', res.range.max = format_floating_max (type, 'e',
-1 == prec ? 6 : prec); -1 == dir.prec ? 6 : dir.prec);
} }
break; break;
} }
...@@ -1448,14 +1471,14 @@ format_floating (const conversion_spec &spec, HOST_WIDE_INT width, ...@@ -1448,14 +1471,14 @@ format_floating (const conversion_spec &spec, HOST_WIDE_INT width,
when precision is greater than zero, then the lower bound when precision is greater than zero, then the lower bound
is 2 plus precision (plus flags). */ is 2 plus precision (plus flags). */
res.range.min = (flagmin res.range.min = (flagmin
+ (prec != INT_MIN) /* for decimal point */ + (dir.prec != HOST_WIDE_INT_MIN) /* decimal point */
+ (prec == INT_MIN + (dir.prec == HOST_WIDE_INT_MIN
? 0 : prec < 0 ? 6 : prec ? prec : -1)); ? 0 : dir.prec < 0 ? 6 : dir.prec ? dir.prec : -1));
if (res.range.max == HOST_WIDE_INT_M1U) if (res.range.max == HOST_WIDE_INT_M1U)
{ {
/* Compute the upper bound for -TYPE_MAX. */ /* Compute the upper bound for -TYPE_MAX. */
res.range.max = format_floating_max (type, 'f', prec); res.range.max = format_floating_max (type, 'f', dir.prec);
} }
break; break;
} }
...@@ -1472,7 +1495,7 @@ format_floating (const conversion_spec &spec, HOST_WIDE_INT width, ...@@ -1472,7 +1495,7 @@ format_floating (const conversion_spec &spec, HOST_WIDE_INT width,
{ {
/* Compute the upper bound for -TYPE_MAX which should be /* Compute the upper bound for -TYPE_MAX which should be
the lesser of %e and %f. */ the lesser of %e and %f. */
res.range.max = format_floating_max (type, 'g', prec); res.range.max = format_floating_max (type, 'g', dir.prec);
} }
break; break;
} }
...@@ -1481,148 +1504,118 @@ format_floating (const conversion_spec &spec, HOST_WIDE_INT width, ...@@ -1481,148 +1504,118 @@ format_floating (const conversion_spec &spec, HOST_WIDE_INT width,
return fmtresult (); return fmtresult ();
} }
if (width > 0) if (dir.width > 0)
{ {
/* If width has been specified use it to adjust the range. */ /* If width has been specified use it to adjust the range. */
if (res.range.min < (unsigned)width) if (res.range.min < (unsigned)dir.width)
res.range.min = width; res.range.min = dir.width;
if (res.range.max < (unsigned)width) if (res.range.max < (unsigned)dir.width)
res.range.max = width; res.range.max = dir.width;
} }
return res; return res;
} }
/* 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 DIR will write on output for the
floating argument ARG. */ floating argument ARG. */
static fmtresult static fmtresult
format_floating (const conversion_spec &spec, tree arg) format_floating (const directive &dir, tree arg)
{ {
/* Set WIDTH to -1 when it's not specified, to HOST_WIDE_INT_MIN when if (!arg || TREE_CODE (arg) != REAL_CST)
it is specified by the asterisk to an unknown value, and otherwise return format_floating (dir);
to a non-negative value corresponding to the specified width. */
HOST_WIDE_INT width = -1;
HOST_WIDE_INT prec = -1;
/* The minimum and maximum number of bytes produced by the directive. */ HOST_WIDE_INT prec = dir.prec;
fmtresult res;
res.constant = arg && TREE_CODE (arg) == REAL_CST;
if (spec.have_width) if (prec < 0 && TOUPPER (dir.specifier) != 'A')
width = spec.width;
else if (spec.star_width)
{
if (TREE_CODE (spec.star_width) == INTEGER_CST)
{
width = tree_to_shwi (spec.star_width);
if (width < 0)
width = -width;
}
else
width = INT_MIN;
}
if (spec.have_precision)
prec = spec.precision;
else if (spec.star_precision)
{
if (TREE_CODE (spec.star_precision) == INTEGER_CST)
{
prec = tree_to_shwi (spec.star_precision);
if (prec < 0)
prec = -1;
}
else
prec = INT_MIN;
}
else if (res.constant && TOUPPER (spec.specifier) != 'A')
{ {
/* Specify the precision explicitly since mpfr_sprintf defaults /* Specify the precision explicitly since mpfr_sprintf defaults
to zero. */ to zero. */
prec = 6; prec = 6;
} }
if (res.constant) /* The minimum and maximum number of bytes produced by the directive. */
{ fmtresult res;
/* Get the real type format desription for the target. */ res.constant = true;
const REAL_VALUE_TYPE *rvp = TREE_REAL_CST_PTR (arg);
const real_format *rfmt = REAL_MODE_FORMAT (TYPE_MODE (TREE_TYPE (arg))); /* Get the real type format desription for the target. */
const REAL_VALUE_TYPE *rvp = TREE_REAL_CST_PTR (arg);
const real_format *rfmt = REAL_MODE_FORMAT (TYPE_MODE (TREE_TYPE (arg)));
/* Convert the GCC real value representation with the precision char fmtstr [40];
of the real type to the mpfr_t format with the GCC default char *pfmt = fmtstr;
round-to-nearest mode. */
mpfr_t mpfrval;
mpfr_init2 (mpfrval, rfmt->p);
mpfr_from_real (mpfrval, rvp, GMP_RNDN);
char fmtstr [40]; /* Append flags. */
char *pfmt = fmtstr; for (const char *pf = "-+ #0"; *pf; ++pf)
if (dir.get_flag (*pf))
*pfmt++ = *pf;
/* Append flags. */ *pfmt = '\0';
for (const char *pf = "-+ #0"; *pf; ++pf)
if (spec.get_flag (*pf))
*pfmt++ = *pf;
*pfmt = '\0'; {
/* Set up an array to easily iterate over. */
unsigned HOST_WIDE_INT* const minmax[] = {
&res.range.min, &res.range.max
};
for (int i = 0; i != sizeof minmax / sizeof *minmax; ++i)
{ {
/* Set up an array to easily iterate over below. */ /* Convert the GCC real value representation with the precision
unsigned HOST_WIDE_INT* const minmax[] = { of the real type to the mpfr_t format rounding down in the
&res.range.min, &res.range.max first iteration that computes the minimm and up in the second
}; that computes the maximum. This order is arbibtrary because
rounding in either direction can result in longer output. */
for (int i = 0; i != sizeof minmax / sizeof *minmax; ++i) mpfr_t mpfrval;
{ mpfr_init2 (mpfrval, rfmt->p);
/* Use the MPFR rounding specifier to round down in the first mpfr_from_real (mpfrval, rvp, i ? MPFR_RNDU : MPFR_RNDD);
iteration and then up. In most but not all cases this will
result in the same number of bytes. */ /* Use the MPFR rounding specifier to round down in the first
char rndspec = "DU"[i]; iteration and then up. In most but not all cases this will
result in the same number of bytes. */
/* Format it and store the result in the corresponding member char rndspec = "DU"[i];
of the result struct. */
unsigned HOST_WIDE_INT len /* Format it and store the result in the corresponding member
= get_mpfr_format_length (mpfrval, fmtstr, prec, of the result struct. */
spec.specifier, rndspec); unsigned HOST_WIDE_INT len
if (0 < width && len < (unsigned)width) = get_mpfr_format_length (mpfrval, fmtstr, prec,
len = width; dir.specifier, rndspec);
*minmax[i] = len; if (0 < dir.width && len < (unsigned)dir.width)
} len = dir.width;
*minmax[i] = len;
} }
}
/* Make sure the minimum is less than the maximum (MPFR rounding /* Make sure the minimum is less than the maximum (MPFR rounding
in the call to mpfr_snprintf can result in the reverse. */ in the call to mpfr_snprintf can result in the reverse. */
if (res.range.max < res.range.min) if (res.range.max < res.range.min)
{ {
unsigned HOST_WIDE_INT tmp = res.range.min; unsigned HOST_WIDE_INT tmp = res.range.min;
res.range.min = res.range.max; res.range.min = res.range.max;
res.range.max = tmp; res.range.max = tmp;
} }
/* The range of output is known even if the result isn't bounded. */
if (width == HOST_WIDE_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 = (res.knownrange
&& (TOUPPER (spec.specifier) != 'A'
|| (0 <= prec && (unsigned) prec < target_int_max ()))
&& res.range.min < target_int_max ());
return res; /* The range of output is known even if the result isn't bounded. */
if (dir.width == HOST_WIDE_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 = (res.knownrange
&& (TOUPPER (dir.specifier) != 'A'
|| (0 <= dir.prec && (unsigned) dir.prec < target_int_max ()))
&& res.range.min < target_int_max ());
return format_floating (spec, width, prec); return res;
} }
/* Return a FMTRESULT struct set to the lengths of the shortest and longest /* Return a FMTRESULT struct set to the lengths of the shortest and longest
...@@ -1681,191 +1674,211 @@ get_string_length (tree str) ...@@ -1681,191 +1674,211 @@ get_string_length (tree str)
} }
/* Return the minimum and maximum number of characters formatted /* Return the minimum and maximum number of characters formatted
by the '%c' and '%s' format directives and ther wide character by the '%c' format directives and its wide character form for
forms for the argument ARG. ARG can be null (for functions the argument ARG. ARG can be null (for functions such as
such as vsprinf). */ vsprinf). */
static fmtresult static fmtresult
format_string (const conversion_spec &spec, tree arg) format_character (const directive &dir, tree arg)
{ {
/* 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; fmtresult res;
/* The maximum number of bytes for an unknown wide character argument /* The maximum number of bytes for an unknown wide character argument
to a "%lc" directive adjusted for precision but not field width. to a "%lc" directive adjusted for precision but not field width.
6 is the longest UTF-8 sequence for a single wide character. */ 6 is the longest UTF-8 sequence for a single wide character. */
const unsigned HOST_WIDE_INT max_bytes_for_unknown_wc const unsigned HOST_WIDE_INT max_bytes_for_unknown_wc
= (0 <= prec ? prec : warn_format_overflow > 1 ? 6 : 1); = (0 <= dir.prec ? dir.prec : warn_format_overflow > 1 ? 6 : 1);
if (dir.modifier == FMT_LEN_l)
{
/* Positive if the argument is a wide NUL character? */
int nul = (arg && TREE_CODE (arg) == INTEGER_CST
? integer_zerop (arg) : -1);
/* A '%lc' directive is the same as '%ls' for a two element
wide string character with the second element of NUL, so
when the character is unknown the minimum number of bytes
is the smaller of either 0 (at level 1) or 1 (at level 2)
and WIDTH, and the maximum is MB_CUR_MAX in the selected
locale, which is unfortunately, unknown. */
res.range.min = warn_format_overflow == 1 ? !nul : nul < 1;
res.range.max = max_bytes_for_unknown_wc;
/* The range above is good enough to issue warnings but not
for value range propagation, so clear BOUNDED. */
res.bounded = false;
}
else
{
/* A plain '%c' directive. Its ouput is exactly 1. */
res.range.min = res.range.max = 1;
res.bounded = true;
res.knownrange = true;
res.constant = arg && TREE_CODE (arg) == INTEGER_CST;
}
/* Adjust the lengths for field width. */
if (0 < dir.width)
{
if (res.range.min < (unsigned HOST_WIDE_INT)dir.width)
res.range.min = dir.width;
if (res.range.max < (unsigned HOST_WIDE_INT)dir.width)
res.range.max = dir.width;
/* Adjust BOUNDED if width happens to make them equal. */
if (res.range.min == res.range.max && res.range.min < target_int_max ())
res.bounded = true;
}
/* When precision is specified the range of characters on output
is known to be bounded by it. */
if (-1 < dir.width && -1 < dir.prec)
res.knownrange = true;
return res;
}
/* Return the minimum and maximum number of characters formatted
by the '%c' and '%s' format directives and ther wide character
forms for the argument ARG. ARG can be null (for functions
such as vsprinf). */
static fmtresult
format_string (const directive &dir, tree arg)
{
fmtresult res;
/* The maximum number of bytes for an unknown string argument to either /* The maximum number of bytes for an unknown string argument to either
a "%s" or "%ls" directive adjusted for precision but not field width. */ a "%s" or "%ls" directive adjusted for precision but not field width. */
const unsigned HOST_WIDE_INT max_bytes_for_unknown_str const unsigned HOST_WIDE_INT max_bytes_for_unknown_str
= (0 <= prec ? prec : warn_format_overflow > 1); = (0 <= dir.prec ? dir.prec : warn_format_overflow > 1);
/* The result is bounded unless overriddden for a non-constant string /* The result is bounded unless overriddden for a non-constant string
of an unknown length. */ of an unknown length. */
bool bounded = true; bool bounded = true;
if (spec.specifier == 'c') /* Compute the range the argument's length can be in. */
{ fmtresult slen = get_string_length (arg);
if (spec.modifier == FMT_LEN_l) if (slen.constant)
{
/* Positive if the argument is a wide NUL character? */
int nul = (arg && TREE_CODE (arg) == INTEGER_CST
? integer_zerop (arg) : -1);
/* A '%lc' directive is the same as '%ls' for a two element
wide string character with the second element of NUL, so
when the character is unknown the minimum number of bytes
is the smaller of either 0 (at level 1) or 1 (at level 2)
and WIDTH, and the maximum is MB_CUR_MAX in the selected
locale, which is unfortunately, unknown. */
res.range.min = warn_format_overflow == 1 ? !nul : nul < 1;
res.range.max = max_bytes_for_unknown_wc;
/* The range above is good enough to issue warnings but not
for value range propagation, so clear BOUNDED. */
res.bounded = false;
}
else
{
/* A plain '%c' directive. Its ouput is exactly 1. */
res.range.min = res.range.max = 1;
res.bounded = true;
res.knownrange = true;
res.constant = arg && TREE_CODE (arg) == INTEGER_CST;
}
}
else /* spec.specifier == 's' */
{ {
/* Compute the range the argument's length can be in. */ gcc_checking_assert (slen.range.min == slen.range.max);
fmtresult slen = get_string_length (arg);
if (slen.constant)
{
gcc_checking_assert (slen.range.min == slen.range.max);
/* A '%s' directive with a string argument with constant length. */
res.range = slen.range;
/* The output of "%s" and "%ls" directives with a constant /* A '%s' directive with a string argument with constant length. */
string is in a known range unless width of an unknown value res.range = slen.range;
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) /* The output of "%s" and "%ls" directives with a constant
{ string is in a known range unless width of an unknown value
bounded = false; 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 = HOST_WIDE_INT_MIN != dir.width;
if (warn_format_overflow > 1) if (dir.modifier == FMT_LEN_l)
{ {
/* Leave the minimum number of bytes the wide string bounded = false;
converts to equal to its length and set the maximum
to the worst case length which is the string length
multiplied by MB_LEN_MAX. */
/* It's possible to be smarter about computing the maximum
by scanning the wide string for any 8-bit characters and
if it contains none, using its length for the maximum.
Even though this would be simple to do it's unlikely to
be worth it when dealing with wide characters. */
res.range.max *= target_mb_len_max;
}
/* For a wide character string, use precision as the maximum if (warn_format_overflow > 1)
even if precision is greater than the string length since
the number of bytes the string converts to may be greater
(due to MB_CUR_MAX). */
if (0 <= prec)
res.range.max = prec;
}
else if (0 <= width)
{ {
/* The output of a "%s" directive with a constant argument /* Leave the minimum number of bytes the wide string
and constant or no width is bounded. It is constant if converts to equal to its length and set the maximum
precision is either not specified or it is specified and to the worst case length which is the string length
its value is known. */ multiplied by MB_LEN_MAX. */
res.bounded = true;
res.constant = prec != HOST_WIDE_INT_MIN; /* It's possible to be smarter about computing the maximum
} by scanning the wide string for any 8-bit characters and
else if (width == HOST_WIDE_INT_MIN) if it contains none, using its length for the maximum.
{ Even though this would be simple to do it's unlikely to
/* Specified but unknown width makes the output unbounded. */ be worth it when dealing with wide characters. */
res.range.max = HOST_WIDE_INT_MAX; res.range.max *= target_mb_len_max();
} }
if (0 <= prec && (unsigned HOST_WIDE_INT)prec < res.range.min) /* For a wide character string, use precision as the maximum
{ even if precision is greater than the string length since
res.range.min = prec; the number of bytes the string converts to may be greater
res.range.max = prec; (due to MB_CUR_MAX). */
} if (0 <= dir.prec)
else if (prec == HOST_WIDE_INT_MIN) res.range.max = dir.prec;
{
/* When precision is specified but not known the lower
bound is assumed to be as low as zero. */
res.range.min = 0;
}
} }
else if (arg && integer_zerop (arg)) else if (-1 <= dir.width)
{ {
/* Handle null pointer argument. */ /* The output of a "%s" directive with a constant argument
and constant or no width is bounded. It is constant if
fmtresult res; precision is either not specified or it is specified and
res.range.min = 0; its value is known. */
res.range.max = HOST_WIDE_INT_MAX; res.bounded = true;
res.nullp = true; res.constant = dir.prec != HOST_WIDE_INT_MIN;
return res;
} }
else else if (dir.width == HOST_WIDE_INT_MIN)
{ {
/* For a '%s' and '%ls' directive with a non-constant string, /* Specified but unknown width makes the output unbounded. */
the minimum number of characters is the greater of WIDTH res.range.max = HOST_WIDE_INT_MAX;
and either 0 in mode 1 or the smaller of PRECISION and 1 }
in mode 2, and the maximum is PRECISION or -1 to disable
tracking. */
if (0 <= prec) if (0 <= dir.prec && (unsigned HOST_WIDE_INT)dir.prec < res.range.min)
{ {
if (slen.range.min >= target_int_max ()) res.range.min = dir.prec;
slen.range.min = 0; res.range.max = dir.prec;
else if ((unsigned HOST_WIDE_INT)prec < slen.range.min) }
slen.range.min = prec; else if (dir.prec == HOST_WIDE_INT_MIN)
{
if ((unsigned HOST_WIDE_INT)prec < slen.range.max /* When precision is specified but not known the lower
|| slen.range.max >= target_int_max ()) bound is assumed to be as low as zero. */
slen.range.max = prec; res.range.min = 0;
} }
else if (slen.range.min >= target_int_max ()) }
{ else if (arg && integer_zerop (arg))
slen.range.min = max_bytes_for_unknown_str; {
slen.range.max = max_bytes_for_unknown_str; /* Handle null pointer argument. */
bounded = false;
}
res.range = slen.range; fmtresult res (0);
res.nullp = true;
return res;
}
else
{
/* For a '%s' and '%ls' directive with a non-constant string,
the minimum number of characters is the greater of WIDTH
and either 0 in mode 1 or the smaller of PRECISION and 1
in mode 2, and the maximum is PRECISION or -1 to disable
tracking. */
/* The output is considered bounded when a precision has been if (0 <= dir.prec)
specified to limit the number of bytes or when the number {
of bytes is known or contrained to some range. */ if (slen.range.min >= target_int_max ())
res.bounded = 0 <= prec || slen.bounded; slen.range.min = 0;
res.knownrange = slen.knownrange; else if ((unsigned HOST_WIDE_INT)dir.prec < slen.range.min)
res.constant = false; slen.range.min = dir.prec;
if ((unsigned HOST_WIDE_INT)dir.prec < slen.range.max
|| slen.range.max >= target_int_max ())
slen.range.max = dir.prec;
}
else if (slen.range.min >= target_int_max ())
{
slen.range.min = max_bytes_for_unknown_str;
slen.range.max = max_bytes_for_unknown_str;
bounded = false;
} }
res.range = slen.range;
/* The output is considered bounded when a precision has been
specified to limit the number of bytes or when the number
of bytes is known or contrained to some range. */
res.bounded = 0 <= dir.prec || slen.bounded;
res.knownrange = slen.knownrange;
res.constant = false;
} }
/* Adjust the lengths for field width. */ /* Adjust the lengths for field width. */
if (0 < width) if (0 < dir.width)
{ {
if (res.range.min < (unsigned HOST_WIDE_INT)width) if (res.range.min < (unsigned HOST_WIDE_INT)dir.width)
res.range.min = width; res.range.min = dir.width;
if (res.range.max < (unsigned HOST_WIDE_INT)width) if (res.range.max < (unsigned HOST_WIDE_INT)dir.width)
res.range.max = width; res.range.max = dir.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 ()
...@@ -1875,22 +1888,25 @@ format_string (const conversion_spec &spec, tree arg) ...@@ -1875,22 +1888,25 @@ format_string (const conversion_spec &spec, 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 (-1 < width && -1 < prec) if (HOST_WIDE_INT_MIN != dir.width && HOST_WIDE_INT_MIN != dir.prec)
res.knownrange = true; res.knownrange = true;
return res; return res;
} }
/* Compute the length of the output resulting from the conversion /* Compute the length of the output resulting from the conversion
specification SPEC with the argument ARG in a call described by INFO specification DIR with the argument ARG in a call described by INFO
and update the overall result of the call in *RES. The format directive and update the overall result of the call in *RES. The format directive
corresponding to SPEC starts at CVTBEG and is CVTLEN characters long. */ corresponding to DIR starts at CVTBEG and is CVTLEN characters long. */
static void static bool
format_directive (const pass_sprintf_length::call_info &info, format_directive (const pass_sprintf_length::call_info &info,
format_result *res, const char *cvtbeg, size_t cvtlen, format_result *res, const directive &dir)
const conversion_spec &spec, tree arg)
{ {
const char *cvtbeg = dir.beg;
size_t cvtlen = dir.len;
tree arg = dir.arg;
/* Offset of the beginning of the directive from the beginning /* Offset of the beginning of the directive from the beginning
of the format string. */ of the format string. */
size_t offset = cvtbeg - info.fmtstr; size_t offset = cvtbeg - info.fmtstr;
...@@ -1914,11 +1930,11 @@ format_directive (const pass_sprintf_length::call_info &info, ...@@ -1914,11 +1930,11 @@ format_directive (const pass_sprintf_length::call_info &info,
/* Bail when there is no function to compute the output length, /* Bail when there is no function to compute the output length,
or when minimum length checking has been disabled. */ or when minimum length checking has been disabled. */
if (!spec.fmtfunc || res->number_chars_min >= HOST_WIDE_INT_MAX) if (!dir.fmtfunc || res->number_chars_min >= HOST_WIDE_INT_MAX)
return; return false;
/* Compute the (approximate) length of the formatted output. */ /* Compute the (approximate) length of the formatted output. */
fmtresult fmtres = spec.fmtfunc (spec, arg); fmtresult fmtres = dir.fmtfunc (dir, arg);
/* The overall result is bounded and constant only if the output /* The overall result is bounded and constant only if the output
of every directive is bounded and constant, respectively. */ of every directive is bounded and constant, respectively. */
...@@ -1962,7 +1978,7 @@ format_directive (const pass_sprintf_length::call_info &info, ...@@ -1962,7 +1978,7 @@ format_directive (const pass_sprintf_length::call_info &info,
except in an error) but keep tracking the minimum and maximum except in an error) but keep tracking the minimum and maximum
number of characters. */ number of characters. */
res->number_chars = HOST_WIDE_INT_M1U; res->number_chars = HOST_WIDE_INT_M1U;
return; return true;
} }
} }
...@@ -1976,7 +1992,7 @@ format_directive (const pass_sprintf_length::call_info &info, ...@@ -1976,7 +1992,7 @@ format_directive (const pass_sprintf_length::call_info &info,
res->warned = true; res->warned = true;
res->number_chars = HOST_WIDE_INT_M1U; res->number_chars = HOST_WIDE_INT_M1U;
res->number_chars_min = res->number_chars_max = res->number_chars; res->number_chars_min = res->number_chars_max = res->number_chars;
return; return false;
} }
bool warned = res->warned; bool warned = res->warned;
...@@ -2037,13 +2053,13 @@ format_directive (const pass_sprintf_length::call_info &info, ...@@ -2037,13 +2053,13 @@ format_directive (const pass_sprintf_length::call_info &info,
} }
} }
else if (navail < fmtres.range.max else if (navail < fmtres.range.max
&& (spec.specifier != 's' && (dir.specifier != 's'
|| fmtres.range.max < HOST_WIDE_INT_MAX) || fmtres.range.max < HOST_WIDE_INT_MAX)
&& ((info.bounded && ((info.bounded
&& (!info.retval_used () && (!info.retval_used ()
|| warn_format_trunc > 1)) || warn_format_trunc > 1))
|| (!info.bounded || (!info.bounded
&& (spec.specifier == 's' && (dir.specifier == 's'
|| warn_format_overflow > 1)))) || warn_format_overflow > 1))))
{ {
/* The maximum directive output is longer than there is /* The maximum directive output is longer than there is
...@@ -2198,6 +2214,7 @@ format_directive (const pass_sprintf_length::call_info &info, ...@@ -2198,6 +2214,7 @@ format_directive (const pass_sprintf_length::call_info &info,
} }
res->warned |= warned; res->warned |= warned;
return true;
} }
/* Account for the number of bytes between BEG and END (or between /* Account for the number of bytes between BEG and END (or between
...@@ -2401,318 +2418,424 @@ add_bytes (const pass_sprintf_length::call_info &info, ...@@ -2401,318 +2418,424 @@ add_bytes (const pass_sprintf_length::call_info &info,
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
/* Compute the length of the output resulting from the call to a formatted /* Parse a format directive in function call described by INFO starting
output function described by INFO and store the result of the call in at STR and populate DIR structure. Bump up *ARGNO by the number of
*RES. Issue warnings for detected past the end writes. Return true arguments extracted for the directive. Return the length of
if the complete format string has been processed and *RES can be relied the directive. */
on, false otherwise (e.g., when a unknown or unhandled directive was seen
that caused the processing to be terminated early). */
bool static size_t
pass_sprintf_length::compute_format_length (call_info &info, parse_directive (pass_sprintf_length::call_info &info,
format_result *res) directive &dir, format_result *res,
const char *str, unsigned *argno)
{ {
/* The variadic argument counter. */ const char *pcnt = strchr (str, '%');
unsigned argno = info.argidx; dir.beg = str;
/* Reset exact, minimum, and maximum character counters. */ if (size_t len = pcnt ? pcnt - str : *str ? strlen (str) : 1)
res->number_chars = res->number_chars_min = res->number_chars_max = 0; {
/* This directive is either a plain string or the terminating nul
(which isn't really a directive but it simplifies things to
handle it as if it were). */
dir.len = len;
dir.fmtfunc = NULL;
/* No directive has been seen yet so the length of output is bounded if (dump_file)
by the known range [0, 0] and constant (with no conversion producing {
more than 4K bytes) until determined otherwise. */ fprintf (dump_file, " Directive %u at offset %zu: \"%.*s\", "
res->bounded = true; "length = %zu\n",
res->knownrange = true; dir.dirno, (size_t)(dir.beg - info.fmtstr),
res->constant = true; (int)dir.len, dir.beg, dir.len);
res->under4k = true; }
res->floating = false;
res->warned = false; return len - !*str;
}
const char *pf = pcnt + 1;
/* POSIX numbered argument index or zero when none. */
unsigned dollar = 0;
/* With and precision. -1 when not specified, HOST_WIDE_INT_MIN
when given by a va_list argument, and a non-negative value
when specified in the format string itself. */
HOST_WIDE_INT width = -1;
HOST_WIDE_INT precision = -1;
const char *pf = info.fmtstr; /* Width specified via the asterisk. Need not be INTEGER_CST.
For vararg functions set to void_node. */
tree star_width = NULL_TREE;
/* Width specified via the asterisk. Need not be INTEGER_CST.
For vararg functions set to void_node. */
tree star_precision = NULL_TREE;
if (ISDIGIT (*pf))
{
/* This could be either a POSIX positional argument, the '0'
flag, or a width, depending on what follows. Store it as
width and sort it out later after the next character has
been seen. */
char *end;
width = strtol (pf, &end, 10);
pf = end;
}
else if ('*' == *pf)
{
/* Similarly to the block above, this could be either a POSIX
positional argument or a width, depending on what follows. */
if (*argno < gimple_call_num_args (info.callstmt))
star_width = gimple_call_arg (info.callstmt, (*argno)++);
else
star_width = void_node;
++pf;
}
for ( ; ; ) if (*pf == '$')
{ {
/* The beginning of the next format directive. */ /* Handle the POSIX dollar sign which references the 1-based
const char *dir = strchr (pf, '%'); positional argument number. */
if (width != -1)
dollar = width + info.argidx;
else if (star_width
&& TREE_CODE (star_width) == INTEGER_CST)
dollar = width + tree_to_shwi (star_width);
/* Add the number of bytes between the end of the last directive /* Bail when the numbered argument is out of range (it will
and either the next if one exists, or the end of the format have already been diagnosed by -Wformat). */
string. */ if (dollar == 0
add_bytes (info, pf, dir, res); || dollar == info.argidx
|| dollar > gimple_call_num_args (info.callstmt))
return false;
if (!dir) --dollar;
break;
pf = dir + 1; star_width = NULL_TREE;
width = -1;
++pf;
}
if (0 && *pf == 0) if (dollar || !star_width)
{
if (width != -1)
{ {
/* Incomplete directive. */ if (width == 0)
return false; {
/* 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. */
width = -1;
dir.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
width. */
for ( ; ; ) {
switch (*pf)
{
case ' ':
case '0':
case '+':
case '-':
case '#':
dir.set_flag (*pf++);
break;
default:
goto start_width;
}
}
conversion_spec spec = conversion_spec (); start_width:
/* POSIX numbered argument index or zero when none. */
unsigned dollar = 0;
if (ISDIGIT (*pf)) if (ISDIGIT (*pf))
{ {
/* This could be either a POSIX positional argument, the '0'
flag, or a width, depending on what follows. Store it as
width and sort it out later after the next character has
been seen. */
char *end; char *end;
spec.width = strtol (pf, &end, 10); width = strtol (pf, &end, 10);
spec.have_width = true;
pf = end; pf = end;
} }
else if ('*' == *pf) else if ('*' == *pf)
{ {
/* Similarly to the block above, this could be either a POSIX if (*argno < gimple_call_num_args (info.callstmt))
positional argument or a width, depending on what follows. */ star_width = gimple_call_arg (info.callstmt, (*argno)++);
if (argno < gimple_call_num_args (info.callstmt))
spec.star_width = gimple_call_arg (info.callstmt, argno++);
else else
spec.star_width = void_node; {
/* This is (likely) a va_list. It could also be an invalid
call with insufficient arguments. */
star_width = void_node;
}
++pf; ++pf;
} }
else if ('\'' == *pf)
if (*pf == '$')
{ {
/* Handle the POSIX dollar sign which references the 1-based /* The POSIX apostrophe indicating a numeric grouping
positional argument number. */ in the current locale. Even though it's possible to
if (spec.have_width) estimate the upper bound on the size of the output
dollar = spec.width + info.argidx; based on the number of digits it probably isn't worth
else if (spec.star_width continuing. */
&& TREE_CODE (spec.star_width) == INTEGER_CST) return 0;
dollar = spec.width + tree_to_shwi (spec.star_width); }
}
/* Bail when the numbered argument is out of range (it will
have already been diagnosed by -Wformat). */
if (dollar == 0
|| dollar == info.argidx
|| dollar > gimple_call_num_args (info.callstmt))
return false;
--dollar; start_precision:
if ('.' == *pf)
{
++pf;
spec.star_width = NULL_TREE; if (ISDIGIT (*pf))
spec.have_width = false; {
++pf; char *end;
precision = strtol (pf, &end, 10);
pf = end;
} }
else if ('*' == *pf)
if (dollar || !spec.star_width)
{ {
if (spec.have_width) if (*argno < gimple_call_num_args (info.callstmt))
{ star_precision = gimple_call_arg (info.callstmt, (*argno)++);
if (spec.width == 0) else
{
/* 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
width. */
for ( ; ; ) {
switch (*pf)
{
case ' ':
case '0':
case '+':
case '-':
case '#':
spec.set_flag (*pf++);
break;
default:
goto start_width;
}
}
start_width:
if (ISDIGIT (*pf))
{
char *end;
spec.width = strtol (pf, &end, 10);
spec.have_width = true;
pf = end;
}
else if ('*' == *pf)
{
if (argno < gimple_call_num_args (info.callstmt))
spec.star_width = gimple_call_arg (info.callstmt, argno++);
else
spec.star_width = void_node;
++pf;
}
else if ('\'' == *pf)
{ {
/* The POSIX apostrophe indicating a numeric grouping /* This is (likely) a va_list. It could also be an invalid
in the current locale. Even though it's possible to call with insufficient arguments. */
estimate the upper bound on the size of the output star_precision = void_node;
based on the number of digits it probably isn't worth
continuing. */
return false;
} }
++pf;
} }
else
{
/* The decimal precision or the asterisk are optional.
When neither is dirified it's taken to be zero. */
precision = 0;
}
}
start_precision: switch (*pf)
if ('.' == *pf) {
case 'h':
if (pf[1] == 'h')
{ {
++pf; ++pf;
dir.modifier = FMT_LEN_hh;
if (ISDIGIT (*pf))
{
char *end;
spec.precision = strtol (pf, &end, 10);
spec.have_precision = true;
pf = end;
}
else if ('*' == *pf)
{
if (argno < gimple_call_num_args (info.callstmt))
spec.star_precision = gimple_call_arg (info.callstmt, argno++);
else
spec.star_precision = void_node;
++pf;
}
else
{
/* 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;
}
} }
else
dir.modifier = FMT_LEN_h;
++pf;
break;
case 'j':
dir.modifier = FMT_LEN_j;
++pf;
break;
case 'L':
dir.modifier = FMT_LEN_L;
++pf;
break;
switch (*pf) case 'l':
if (pf[1] == 'l')
{ {
case 'h':
if (pf[1] == 'h')
{
++pf;
spec.modifier = FMT_LEN_hh;
}
else
spec.modifier = FMT_LEN_h;
++pf; ++pf;
break; dir.modifier = FMT_LEN_ll;
}
else
dir.modifier = FMT_LEN_l;
++pf;
break;
case 'j': case 't':
spec.modifier = FMT_LEN_j; dir.modifier = FMT_LEN_t;
++pf; ++pf;
break; break;
case 'L': case 'z':
spec.modifier = FMT_LEN_L; dir.modifier = FMT_LEN_z;
++pf; ++pf;
break; break;
}
case 'l': switch (*pf)
if (pf[1] == 'l') {
{ /* Handle a sole '%' character the same as "%%" but since it's
++pf; undefined prevent the result from being folded. */
spec.modifier = FMT_LEN_ll; case '\0':
} --pf;
else res->bounded = false;
spec.modifier = FMT_LEN_l; /* FALLTHRU */
++pf; case '%':
break; dir.fmtfunc = format_percent;
break;
case 't': case 'a':
spec.modifier = FMT_LEN_t; case 'A':
++pf; case 'e':
break; case 'E':
case 'f':
case 'F':
case 'g':
case 'G':
res->floating = true;
dir.fmtfunc = format_floating;
break;
case 'z': case 'd':
spec.modifier = FMT_LEN_z; case 'i':
++pf; case 'o':
break; case 'u':
case 'x':
case 'X':
dir.fmtfunc = format_integer;
break;
case 'p':
/* The %p output is implementation-defined. It's possible
to determine this format but due to extensions (edirially
those of the Linux kernel -- see bug 78512) the first %p
in the format string disables any further processing. */
return false;
case 'n':
/* %n has side-effects even when nothing is actually printed to
any buffer. */
info.nowrite = false;
dir.fmtfunc = format_none;
break;
case 'c':
dir.fmtfunc = format_character;
break;
case 'S':
case 's':
dir.fmtfunc = format_string;
break;
default:
/* Unknown conversion specification. */
return 0;
}
dir.specifier = *pf++;
if (star_width)
{
if (TREE_CODE (TREE_TYPE (star_width)) == INTEGER_TYPE)
dir.set_width (star_width);
else
{
/* Width specified by a va_list takes on the range [0, -INT_MIN]
(width is the absolute value of that specified). */
dir.width = HOST_WIDE_INT_MIN;
} }
}
else
dir.set_width (width);
switch (*pf) if (star_precision)
{
if (TREE_CODE (TREE_TYPE (star_precision)) == INTEGER_TYPE)
dir.set_precision (star_precision);
else
{ {
/* Handle a sole '%' character the same as "%%" but since it's /* Precision specified by a va_list takes on the range [-1, INT_MAX]
undefined prevent the result from being folded. */ (unlike width, negative precision is ignored). */
case '\0': dir.prec = HOST_WIDE_INT_MIN;
--pf; }
res->bounded = false; }
/* FALLTHRU */ else
case '%': dir.set_precision (precision);
spec.fmtfunc = format_percent;
break;
case 'a': /* Extract the argument if the directive takes one and if it's
case 'A': available (e.g., the function doesn't take a va_list). Treat
case 'e': missing arguments the same as va_list, even though they will
case 'E': have likely already been diagnosed by -Wformat. */
case 'f': if (dir.specifier != '%'
case 'F': && *argno < gimple_call_num_args (info.callstmt))
case 'g': dir.arg = gimple_call_arg (info.callstmt, dollar ? dollar : (*argno)++);
case 'G':
res->floating = true;
spec.fmtfunc = format_floating;
break;
case 'd': /* Return the length of the format directive. */
case 'i': dir.len = pf - pcnt;
case 'o':
case 'u':
case 'x':
case 'X':
spec.fmtfunc = format_integer;
break;
case 'p': if (dump_file)
/* The %p output is implementation-defined. It's possible {
to determine this format but due to extensions (especially fprintf (dump_file, " Directive %u at offset %zu: \"%.*s\"",
those of the Linux kernel -- see bug 78512) the first %p dir.dirno, (size_t)(dir.beg - info.fmtstr),
in the format string disables any further processing. */ (int)dir.len, dir.beg);
return false; if (star_width)
fprintf (dump_file, ", width = %lli", (long long)dir.width);
case 'n': if (star_precision)
/* %n has side-effects even when nothing is actually printed to fprintf (dump_file, ", precision = %lli", (long long)dir.prec);
any buffer. */
info.nowrite = false;
break;
case 'c': fputc ('\n', dump_file);
case 'S': }
case 's':
spec.fmtfunc = format_string;
break;
default: return dir.len;
/* Unknown conversion specification. */ }
return false;
}
spec.specifier = *pf++; /* Compute the length of the output resulting from the call to a formatted
output function described by INFO and store the result of the call in
*RES. Issue warnings for detected past the end writes. Return true
if the complete format string has been processed and *RES can be relied
on, false otherwise (e.g., when a unknown or unhandled directive was seen
that caused the processing to be terminated early). */
/* Compute the length of the format directive. */ bool
size_t dirlen = pf - dir; pass_sprintf_length::compute_format_length (call_info &info,
format_result *res)
{
/* Reset exact, minimum, and maximum character counters. */
res->number_chars = res->number_chars_min = res->number_chars_max = 0;
/* No directive has been seen yet so the length of output is bounded
by the known range [0, 0] and constant (with no conversion producing
more than 4K bytes) until determined otherwise. */
res->bounded = true;
res->knownrange = true;
res->constant = true;
res->under4k = true;
res->floating = false;
res->warned = false;
/* 1-based directive counter. */
unsigned dirno = 1;
/* The variadic argument counter. */
unsigned argno = info.argidx;
for (const char *pf = info.fmtstr; ; ++dirno)
{
directive dir = directive ();
dir.dirno = dirno;
size_t n = parse_directive (info, dir, res, pf, &argno);
if (dir.fmtfunc)
{
/* Return failure if the format function fails. */
if (!format_directive (info, res, dir))
return false;
}
else
{
/* Add the number of bytes between the end of the last directive
and either the next if one exists, or the end of the format
string. */
add_bytes (info, pf, n ? pf + n : NULL, res);
}
/* Extract the argument if the directive takes one and if it's /* Return success the directive is zero bytes long and it's
available (e.g., the function doesn't take a va_list). Treat the last think in the format string (i.e., it's the terminating
missing arguments the same as va_list, even though they will nul, which isn't really a directive but handling it as one makes
have likely already been diagnosed by -Wformat. */ things simpler). */
tree arg = NULL_TREE; if (!n)
if (spec.specifier != '%' return *pf == '\0';
&& argno < gimple_call_num_args (info.callstmt))
arg = gimple_call_arg (info.callstmt, dollar ? dollar : argno++);
::format_directive (info, res, dir, dirlen, spec, arg); pf += n;
} }
/* Complete format string was processed (with or without warnings). */ /* Complete format string was processed (with or without warnings). */
......
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