/* The likely worst case value of MB_LEN_MAX for the target, large enough
for UTF-8. Ideally, this would be obtained by a target hook if it were
to be used for optimization but it's good enough as is for warnings. */
-#define target_mb_len_max 6
+#define target_mb_len_max() 6
/* 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
not optimizing and the pass is being invoked early, or when
optimizing and the pass is being invoked during optimization
(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);
}
}
/* Increment the number of output characters by N. */
- 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;
- }
+ format_result& operator+= (unsigned HOST_WIDE_INT);
};
+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. */
static inline HOST_WIDE_INT
struct fmtresult
{
- fmtresult ()
- : argmin (), argmax (), knownrange (), bounded (), constant (), nullp ()
+ /* Construct a FMTRESULT object with all counters initialized
+ 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. */
/* 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. */
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.
- For vararg functions set to void_node. */
- tree star_width;
- /* Precision specified via the asterisk. Need not be INTEGER_CST.
- For vararg functions set to void_node. */
- tree star_precision;
+ /* The specified width, or -1 if not specified. */
+ HOST_WIDE_INT width;
+ /* The specified precision, or -1 if not specified. */
+ HOST_WIDE_INT prec;
/* Length modifier. */
format_lengths modifier;
/* Format specifier character. */
char specifier;
- /* Numeric width was given. */
- unsigned have_width: 1;
- /* Numeric precision was given. */
- 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;
+ /* The argument of the directive or null when the directive doesn't
+ take one or when none is available (such as for vararg functions). */
+ tree arg;
/* Format conversion function that given a conversion specification
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. */
bool get_flag (char chr) const
flags[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 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. */
static fmtresult
-format_percent (const conversion_spec &, tree)
+format_percent (const directive &, tree)
{
- fmtresult res;
- res.argmin = res.argmax = NULL_TREE;
- res.range.min = res.range.max = 1;
+ fmtresult res (1);
res.bounded = res.constant = true;
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 = 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
argument, due to the conversion from either *ARGMIN or *ARGMAX to
the type of the directive's formal argument it's possible for both
}
/* 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
functions). */
static fmtresult
-format_integer (const conversion_spec &spec, tree arg)
+format_integer (const directive &dir, tree arg)
{
tree intmax_type_node;
tree uintmax_type_node;
/* Set WIDTH and PRECISION based on the specification. */
- HOST_WIDE_INT width;
- HOST_WIDE_INT prec;
- get_width_and_precision (spec, &width, &prec);
+ HOST_WIDE_INT width = dir.width;
+ HOST_WIDE_INT prec = dir.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. */
tree dirtype = NULL_TREE;
/* Determine the expected type of the argument from the length
modifier. */
- switch (spec.modifier)
+ switch (dir.modifier)
{
case FMT_LEN_none:
- if (spec.specifier == 'p')
+ if (dir.specifier == 'p')
dirtype = ptr_type_node;
else
dirtype = sign ? integer_type_node : unsigned_type_node;
/* True when a signed conversion is preceded by a sign or space. */
bool maybesign = false;
- switch (spec.specifier)
+ switch (dir.specifier)
{
case 'd':
case 'i':
/* Space and '+' are only meaningful for signed conversions. */
- maybesign = spec.get_flag (' ') | spec.get_flag ('+');
+ maybesign = dir.get_flag (' ') | dir.get_flag ('+');
base = 10;
break;
case 'u':
when it results in just one byte (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 = ((base == 8 && spec.get_flag ('#')) || maybesign);
+ len = ((base == 8 && dir.get_flag ('#')) || maybesign);
}
else
{
/* True when a conversion is preceded by a prefix indicating the base
of the argument (octal or hexadecimal). */
- bool maybebase = spec.get_flag ('#');
+ bool maybebase = dir.get_flag ('#');
len = tree_digits (arg, base, prec, maybesign, maybebase);
if (len < 1)
len = HOST_WIDE_INT_MAX;
if (code == INTEGER_CST)
{
arg = gimple_assign_rhs1 (def);
- return format_integer (spec, arg);
+ return format_integer (dir, arg);
}
if (code == NOP_EXPR)
/* For unsigned conversions/directives, use the minimum (i.e., 0
or 1) and maximum to compute the shortest and longest output,
respectively. */
- res.range.min = format_integer (spec, argmin).range.min;
- res.range.max = format_integer (spec, argmax).range.max;
+ res.range.min = format_integer (dir, argmin).range.min;
+ res.range.max = format_integer (dir, argmax).range.max;
}
else
{
to compute the longest output. This is important when precision
is specified but unknown because otherwise both output lengths
would reflect the largest possible precision (i.e., INT_MAX). */
- res.range.min = format_integer (spec, argmax).range.min;
- res.range.max = format_integer (spec, argmin).range.max;
+ res.range.min = format_integer (dir, argmax).range.min;
+ res.range.max = format_integer (dir, argmin).range.max;
}
/* The result is bounded either when the argument is determined to be
const real_format *rfmt = REAL_MODE_FORMAT (mode);
REAL_VALUE_TYPE rv;
- {
- char buf[256];
- get_max_float (rfmt, buf, sizeof buf);
- real_from_string (&rv, buf);
- }
+ real_maxval (&rv, 0, mode);
/* Convert the GCC real value representation with the precision
of the real type to the mpfr_t format with the GCC default
}
/* Return a range representing the minimum and maximum number of bytes
- that the conversion specification SPEC will output for any argument
- given the WIDTH and PRECISION (extracted from SPEC). This function
+ that the conversion specification DIR will output for any argument
+ given the WIDTH and PRECISION (extracted from DIR). This function
is used when the directive argument or its value isn't known. */
static fmtresult
-format_floating (const conversion_spec &spec, HOST_WIDE_INT width,
- HOST_WIDE_INT prec)
+format_floating (const directive &dir)
{
tree type;
- switch (spec.modifier)
+ switch (dir.modifier)
{
case FMT_LEN_l:
case FMT_LEN_none:
/* The minimum output as determined by flags. It's always at least 1. */
int flagmin = (1 /* for the first digit */
- + (spec.get_flag ('+') | spec.get_flag (' '))
- + (prec == 0 && spec.get_flag ('#')));
+ + (dir.get_flag ('+') | dir.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
the upper bound is the maximum. Otherwise it will be
else
res.range.max = HOST_WIDE_INT_M1U;
- switch (spec.specifier)
+ switch (dir.specifier)
{
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)
{
/* 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;
/* The minimum output is "[-+]1.234567e+00" regardless
of the value of the actual argument. */
res.range.min = (flagmin
- + (prec == INT_MIN
- ? 0 : prec < 0 ? 7 : prec ? prec + 1 : 0)
+ + (dir.prec == HOST_WIDE_INT_MIN
+ ? 0 : dir.prec < 0 ? 7 : dir.prec ? dir.prec + 1 : 0)
+ 2 /* e+ */ + 2);
if (res.range.max == HOST_WIDE_INT_M1U)
/* MPFR uses a precision of 16 by default for some reason.
Set it to the C default of 6. */
res.range.max = format_floating_max (type, 'e',
- -1 == prec ? 6 : prec);
+ -1 == dir.prec ? 6 : dir.prec);
}
break;
}
when precision is greater than zero, then the lower bound
is 2 plus precision (plus flags). */
res.range.min = (flagmin
- + (prec != INT_MIN) /* for decimal point */
- + (prec == INT_MIN
- ? 0 : prec < 0 ? 6 : prec ? prec : -1));
+ + (dir.prec != HOST_WIDE_INT_MIN) /* decimal point */
+ + (dir.prec == HOST_WIDE_INT_MIN
+ ? 0 : dir.prec < 0 ? 6 : dir.prec ? dir.prec : -1));
if (res.range.max == HOST_WIDE_INT_M1U)
{
/* 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;
}
{
/* Compute the upper bound for -TYPE_MAX which should be
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;
}
return fmtresult ();
}
- if (width > 0)
+ if (dir.width > 0)
{
/* If width has been specified use it to adjust the range. */
- if (res.range.min < (unsigned)width)
- res.range.min = width;
- if (res.range.max < (unsigned)width)
- res.range.max = width;
+ if (res.range.min < (unsigned)dir.width)
+ res.range.min = dir.width;
+ if (res.range.max < (unsigned)dir.width)
+ res.range.max = dir.width;
}
return res;
}
/* 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. */
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
- it is specified by the asterisk to an unknown value, and otherwise
- to a non-negative value corresponding to the specified width. */
- HOST_WIDE_INT width = -1;
- HOST_WIDE_INT prec = -1;
+ if (!arg || TREE_CODE (arg) != REAL_CST)
+ return format_floating (dir);
- /* The minimum and maximum number of bytes produced by the directive. */
- fmtresult res;
- res.constant = arg && TREE_CODE (arg) == REAL_CST;
+ HOST_WIDE_INT prec = dir.prec;
- if (spec.have_width)
- 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')
+ if (prec < 0 && TOUPPER (dir.specifier) != 'A')
{
/* Specify the precision explicitly since mpfr_sprintf defaults
to zero. */
prec = 6;
}
- if (res.constant)
- {
- /* 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)));
+ /* The minimum and maximum number of bytes produced by the directive. */
+ fmtresult res;
+ res.constant = true;
+
+ /* 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
- of the real type to the mpfr_t format with the GCC default
- round-to-nearest mode. */
- mpfr_t mpfrval;
- mpfr_init2 (mpfrval, rfmt->p);
- mpfr_from_real (mpfrval, rvp, GMP_RNDN);
+ char fmtstr [40];
+ char *pfmt = fmtstr;
- char fmtstr [40];
- char *pfmt = fmtstr;
+ /* Append flags. */
+ for (const char *pf = "-+ #0"; *pf; ++pf)
+ if (dir.get_flag (*pf))
+ *pfmt++ = *pf;
- /* Append flags. */
- for (const char *pf = "-+ #0"; *pf; ++pf)
- if (spec.get_flag (*pf))
- *pfmt++ = *pf;
+ *pfmt = '\0';
- *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. */
- unsigned HOST_WIDE_INT* const minmax[] = {
- &res.range.min, &res.range.max
- };
-
- for (int i = 0; i != sizeof minmax / sizeof *minmax; ++i)
- {
- /* Use the MPFR rounding specifier to round down in the first
- iteration and then up. In most but not all cases this will
- result in the same number of bytes. */
- char rndspec = "DU"[i];
-
- /* Format it and store the result in the corresponding member
- of the result struct. */
- unsigned HOST_WIDE_INT len
- = get_mpfr_format_length (mpfrval, fmtstr, prec,
- spec.specifier, rndspec);
- if (0 < width && len < (unsigned)width)
- len = width;
-
- *minmax[i] = len;
- }
+ /* Convert the GCC real value representation with the precision
+ of the real type to the mpfr_t format rounding down in the
+ 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. */
+ mpfr_t mpfrval;
+ mpfr_init2 (mpfrval, rfmt->p);
+ mpfr_from_real (mpfrval, rvp, i ? MPFR_RNDU : MPFR_RNDD);
+
+ /* Use the MPFR rounding specifier to round down in the first
+ iteration and then up. In most but not all cases this will
+ result in the same number of bytes. */
+ char rndspec = "DU"[i];
+
+ /* Format it and store the result in the corresponding member
+ of the result struct. */
+ unsigned HOST_WIDE_INT len
+ = get_mpfr_format_length (mpfrval, fmtstr, prec,
+ dir.specifier, rndspec);
+
+ 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
- in the call to mpfr_snprintf can result in the reverse. */
- if (res.range.max < res.range.min)
- {
- unsigned HOST_WIDE_INT tmp = res.range.min;
- res.range.min = res.range.max;
- 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 ());
+ /* Make sure the minimum is less than the maximum (MPFR rounding
+ in the call to mpfr_snprintf can result in the reverse. */
+ if (res.range.max < res.range.min)
+ {
+ unsigned HOST_WIDE_INT tmp = res.range.min;
+ res.range.min = res.range.max;
+ res.range.max = tmp;
+ }
- 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 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). */
+ by the '%c' format directives and its wide character form for
+ the argument ARG. ARG can be null (for functions such as
+ vsprinf). */
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;
/* The maximum number of bytes for an unknown wide character argument
to a "%lc" directive adjusted for precision but not field width.
6 is the longest UTF-8 sequence for a single wide character. */
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
a "%s" or "%ls" directive adjusted for precision but not field width. */
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
of an unknown length. */
bool bounded = true;
- if (spec.specifier == 'c')
- {
- if (spec.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;
- }
- }
- else /* spec.specifier == 's' */
+ /* Compute the range the argument's length can be in. */
+ fmtresult slen = get_string_length (arg);
+ if (slen.constant)
{
- /* Compute the range the argument's length can be in. */
- 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;
+ gcc_checking_assert (slen.range.min == slen.range.max);
- /* The output of "%s" and "%ls" directives with a constant
- string is in a known range unless width of an unknown value
- is specified. For "%s" it is the length of the string. For
- "%ls" it is in the range [length, length * MB_LEN_MAX].
- (The final range can be further constrained by width and
- precision but it's always known.) */
- res.knownrange = -1 < width;
+ /* A '%s' directive with a string argument with constant length. */
+ res.range = slen.range;
- if (spec.modifier == FMT_LEN_l)
- {
- bounded = false;
+ /* The output of "%s" and "%ls" directives with a constant
+ string is in a known range unless width of an unknown value
+ is specified. For "%s" it is the length of the string. For
+ "%ls" it is in the range [length, length * MB_LEN_MAX].
+ (The final range can be further constrained by width and
+ precision but it's always known.) */
+ res.knownrange = HOST_WIDE_INT_MIN != dir.width;
- if (warn_format_overflow > 1)
- {
- /* Leave the minimum number of bytes the wide string
- converts to equal to its length and set the maximum
- to the worst case length which is the string length
- multiplied by MB_LEN_MAX. */
-
- /* It's possible to be smarter about computing the maximum
- by scanning the wide string for any 8-bit characters and
- if it contains none, using its length for the maximum.
- Even though this would be simple to do it's unlikely to
- be worth it when dealing with wide characters. */
- res.range.max *= target_mb_len_max;
- }
+ if (dir.modifier == FMT_LEN_l)
+ {
+ bounded = false;
- /* For a wide character string, use precision as the maximum
- even if precision is greater than the string length since
- the number of bytes the string converts to may be greater
- (due to MB_CUR_MAX). */
- if (0 <= prec)
- res.range.max = prec;
- }
- else if (0 <= width)
+ if (warn_format_overflow > 1)
{
- /* The output of a "%s" directive with a constant argument
- and constant or no width is bounded. It is constant if
- precision is either not specified or it is specified and
- its value is known. */
- res.bounded = true;
- res.constant = 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;
+ /* Leave the minimum number of bytes the wide string
+ converts to equal to its length and set the maximum
+ to the worst case length which is the string length
+ multiplied by MB_LEN_MAX. */
+
+ /* It's possible to be smarter about computing the maximum
+ by scanning the wide string for any 8-bit characters and
+ if it contains none, using its length for the maximum.
+ Even though this would be simple to do it's unlikely to
+ be worth it when dealing with wide characters. */
+ res.range.max *= target_mb_len_max();
}
- if (0 <= prec && (unsigned HOST_WIDE_INT)prec < res.range.min)
- {
- res.range.min = prec;
- res.range.max = prec;
- }
- else if (prec == HOST_WIDE_INT_MIN)
- {
- /* When precision is specified but not known the lower
- bound is assumed to be as low as zero. */
- res.range.min = 0;
- }
+ /* For a wide character string, use precision as the maximum
+ even if precision is greater than the string length since
+ the number of bytes the string converts to may be greater
+ (due to MB_CUR_MAX). */
+ if (0 <= dir.prec)
+ res.range.max = dir.prec;
}
- else if (arg && integer_zerop (arg))
+ else if (-1 <= dir.width)
{
- /* Handle null pointer argument. */
-
- fmtresult res;
- res.range.min = 0;
- res.range.max = HOST_WIDE_INT_MAX;
- res.nullp = true;
- return res;
+ /* The output of a "%s" directive with a constant argument
+ and constant or no width is bounded. It is constant if
+ precision is either not specified or it is specified and
+ its value is known. */
+ res.bounded = true;
+ res.constant = dir.prec != HOST_WIDE_INT_MIN;
}
- else
+ else if (dir.width == HOST_WIDE_INT_MIN)
{
- /* 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. */
+ /* Specified but unknown width makes the output unbounded. */
+ res.range.max = HOST_WIDE_INT_MAX;
+ }
- if (0 <= prec)
- {
- if (slen.range.min >= target_int_max ())
- slen.range.min = 0;
- else if ((unsigned HOST_WIDE_INT)prec < slen.range.min)
- slen.range.min = prec;
-
- if ((unsigned HOST_WIDE_INT)prec < slen.range.max
- || slen.range.max >= target_int_max ())
- slen.range.max = 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;
- }
+ if (0 <= dir.prec && (unsigned HOST_WIDE_INT)dir.prec < res.range.min)
+ {
+ res.range.min = dir.prec;
+ res.range.max = dir.prec;
+ }
+ else if (dir.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 if (arg && integer_zerop (arg))
+ {
+ /* Handle null pointer argument. */
- 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
- specified to limit the number of bytes or when the number
- of bytes is known or contrained to some range. */
- res.bounded = 0 <= prec || slen.bounded;
- res.knownrange = slen.knownrange;
- res.constant = false;
+ if (0 <= dir.prec)
+ {
+ if (slen.range.min >= target_int_max ())
+ slen.range.min = 0;
+ else if ((unsigned HOST_WIDE_INT)dir.prec < slen.range.min)
+ 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. */
- if (0 < width)
+ if (0 < dir.width)
{
- if (res.range.min < (unsigned HOST_WIDE_INT)width)
- res.range.min = width;
+ if (res.range.min < (unsigned HOST_WIDE_INT)dir.width)
+ res.range.min = dir.width;
- if (res.range.max < (unsigned HOST_WIDE_INT)width)
- res.range.max = 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 ()
/* When precision is specified the range of characters on output
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;
return res;
}
/* 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
- 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_result *res, const char *cvtbeg, size_t cvtlen,
- const conversion_spec &spec, tree arg)
+ format_result *res, const directive &dir)
{
+ const char *cvtbeg = dir.beg;
+ size_t cvtlen = dir.len;
+ tree arg = dir.arg;
+
/* Offset of the beginning of the directive from the beginning
of the format string. */
size_t offset = cvtbeg - info.fmtstr;
/* Bail when there is no function to compute the output length,
or when minimum length checking has been disabled. */
- if (!spec.fmtfunc || res->number_chars_min >= HOST_WIDE_INT_MAX)
- return;
+ if (!dir.fmtfunc || res->number_chars_min >= HOST_WIDE_INT_MAX)
+ return false;
/* 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
of every directive is bounded and constant, respectively. */
except in an error) but keep tracking the minimum and maximum
number of characters. */
res->number_chars = HOST_WIDE_INT_M1U;
- return;
+ return true;
}
}
res->warned = true;
res->number_chars = HOST_WIDE_INT_M1U;
res->number_chars_min = res->number_chars_max = res->number_chars;
- return;
+ return false;
}
bool warned = res->warned;
}
}
else if (navail < fmtres.range.max
- && (spec.specifier != 's'
+ && (dir.specifier != 's'
|| fmtres.range.max < HOST_WIDE_INT_MAX)
&& ((info.bounded
&& (!info.retval_used ()
|| warn_format_trunc > 1))
|| (!info.bounded
- && (spec.specifier == 's'
+ && (dir.specifier == 's'
|| warn_format_overflow > 1))))
{
/* The maximum directive output is longer than there is
}
res->warned |= warned;
+ return true;
}
/* Account for the number of bytes between BEG and END (or between
#pragma GCC diagnostic pop
-/* 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). */
+/* Parse a format directive in function call described by INFO starting
+ at STR and populate DIR structure. Bump up *ARGNO by the number of
+ arguments extracted for the directive. Return the length of
+ the directive. */
-bool
-pass_sprintf_length::compute_format_length (call_info &info,
- format_result *res)
+static size_t
+parse_directive (pass_sprintf_length::call_info &info,
+ directive &dir, format_result *res,
+ const char *str, unsigned *argno)
{
- /* The variadic argument counter. */
- unsigned argno = info.argidx;
+ const char *pcnt = strchr (str, '%');
+ dir.beg = str;
- /* Reset exact, minimum, and maximum character counters. */
- res->number_chars = res->number_chars_min = res->number_chars_max = 0;
+ if (size_t len = pcnt ? pcnt - str : *str ? strlen (str) : 1)
+ {
+ /* 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
- 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;
+ if (dump_file)
+ {
+ fprintf (dump_file, " Directive %u at offset %zu: \"%.*s\", "
+ "length = %zu\n",
+ dir.dirno, (size_t)(dir.beg - info.fmtstr),
+ (int)dir.len, dir.beg, dir.len);
+ }
+
+ 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. */
- const char *dir = strchr (pf, '%');
+ /* Handle the POSIX dollar sign which references the 1-based
+ 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
- and either the next if one exists, or the end of the format
- string. */
- add_bytes (info, pf, dir, res);
+ /* 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;
- if (!dir)
- break;
+ --dollar;
- pf = dir + 1;
+ star_width = NULL_TREE;
+ width = -1;
+ ++pf;
+ }
- if (0 && *pf == 0)
+ if (dollar || !star_width)
+ {
+ if (width != -1)
{
- /* Incomplete directive. */
- return false;
+ if (width == 0)
+ {
+ /* The '0' that has been interpreted as a width above is
+ actually a flag. Reset HAVE_WIDTH, set the '0' flag,
+ and continue processing other flags. */
+ 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 ();
-
- /* POSIX numbered argument index or zero when none. */
- unsigned dollar = 0;
-
+ start_width:
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;
- spec.width = strtol (pf, &end, 10);
- spec.have_width = true;
+ 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))
- spec.star_width = gimple_call_arg (info.callstmt, argno++);
+ if (*argno < gimple_call_num_args (info.callstmt))
+ star_width = gimple_call_arg (info.callstmt, (*argno)++);
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;
}
-
- if (*pf == '$')
+ else if ('\'' == *pf)
{
- /* Handle the POSIX dollar sign which references the 1-based
- positional argument number. */
- if (spec.have_width)
- dollar = spec.width + info.argidx;
- else if (spec.star_width
- && TREE_CODE (spec.star_width) == INTEGER_CST)
- 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;
+ /* The POSIX apostrophe indicating a numeric grouping
+ in the current locale. Even though it's possible to
+ estimate the upper bound on the size of the output
+ based on the number of digits it probably isn't worth
+ continuing. */
+ return 0;
+ }
+ }
- --dollar;
+ start_precision:
+ if ('.' == *pf)
+ {
+ ++pf;
- spec.star_width = NULL_TREE;
- spec.have_width = false;
- ++pf;
+ if (ISDIGIT (*pf))
+ {
+ char *end;
+ precision = strtol (pf, &end, 10);
+ pf = end;
}
-
- if (dollar || !spec.star_width)
+ else if ('*' == *pf)
{
- if (spec.have_width)
- {
- if (spec.width == 0)
- {
- /* The '0' that has been interpreted as a width above is
- actually a flag. Reset HAVE_WIDTH, set the '0' flag,
- and continue processing other flags. */
- spec.have_width = false;
- spec.set_flag ('0');
- }
- else if (!dollar)
- {
- /* (Non-zero) width has been seen. The next character
- is either a period or a digit. */
- goto start_precision;
- }
- }
- /* When either '$' has been seen, or width has not been seen,
- the next field is the optional flags followed by an optional
- 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)
+ if (*argno < gimple_call_num_args (info.callstmt))
+ star_precision = gimple_call_arg (info.callstmt, (*argno)++);
+ else
{
- /* The POSIX apostrophe indicating a numeric grouping
- in the current locale. Even though it's possible to
- estimate the upper bound on the size of the output
- based on the number of digits it probably isn't worth
- continuing. */
- return false;
+ /* This is (likely) a va_list. It could also be an invalid
+ call with insufficient arguments. */
+ star_precision = void_node;
}
+ ++pf;
}
+ else
+ {
+ /* The decimal precision or the asterisk are optional.
+ When neither is dirified it's taken to be zero. */
+ precision = 0;
+ }
+ }
- start_precision:
- if ('.' == *pf)
+ switch (*pf)
+ {
+ case 'h':
+ if (pf[1] == 'h')
{
++pf;
-
- 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;
- }
+ dir.modifier = FMT_LEN_hh;
}
+ 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;
- break;
+ dir.modifier = FMT_LEN_ll;
+ }
+ else
+ dir.modifier = FMT_LEN_l;
+ ++pf;
+ break;
- case 'j':
- spec.modifier = FMT_LEN_j;
- ++pf;
- break;
+ case 't':
+ dir.modifier = FMT_LEN_t;
+ ++pf;
+ break;
- case 'L':
- spec.modifier = FMT_LEN_L;
- ++pf;
- break;
+ case 'z':
+ dir.modifier = FMT_LEN_z;
+ ++pf;
+ break;
+ }
- case 'l':
- if (pf[1] == 'l')
- {
- ++pf;
- spec.modifier = FMT_LEN_ll;
- }
- else
- spec.modifier = FMT_LEN_l;
- ++pf;
- break;
+ switch (*pf)
+ {
+ /* Handle a sole '%' character the same as "%%" but since it's
+ undefined prevent the result from being folded. */
+ case '\0':
+ --pf;
+ res->bounded = false;
+ /* FALLTHRU */
+ case '%':
+ dir.fmtfunc = format_percent;
+ break;
- case 't':
- spec.modifier = FMT_LEN_t;
- ++pf;
- break;
+ case 'a':
+ case 'A':
+ case 'e':
+ case 'E':
+ case 'f':
+ case 'F':
+ case 'g':
+ case 'G':
+ res->floating = true;
+ dir.fmtfunc = format_floating;
+ break;
- case 'z':
- spec.modifier = FMT_LEN_z;
- ++pf;
- break;
+ case 'd':
+ case 'i':
+ case 'o':
+ 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
- undefined prevent the result from being folded. */
- case '\0':
- --pf;
- res->bounded = false;
- /* FALLTHRU */
- case '%':
- spec.fmtfunc = format_percent;
- break;
+ /* Precision specified by a va_list takes on the range [-1, INT_MAX]
+ (unlike width, negative precision is ignored). */
+ dir.prec = HOST_WIDE_INT_MIN;
+ }
+ }
+ else
+ dir.set_precision (precision);
- case 'a':
- case 'A':
- case 'e':
- case 'E':
- case 'f':
- case 'F':
- case 'g':
- case 'G':
- res->floating = true;
- spec.fmtfunc = format_floating;
- break;
+ /* Extract the argument if the directive takes one and if it's
+ available (e.g., the function doesn't take a va_list). Treat
+ missing arguments the same as va_list, even though they will
+ have likely already been diagnosed by -Wformat. */
+ if (dir.specifier != '%'
+ && *argno < gimple_call_num_args (info.callstmt))
+ dir.arg = gimple_call_arg (info.callstmt, dollar ? dollar : (*argno)++);
- case 'd':
- case 'i':
- case 'o':
- case 'u':
- case 'x':
- case 'X':
- spec.fmtfunc = format_integer;
- break;
+ /* Return the length of the format directive. */
+ dir.len = pf - pcnt;
- case 'p':
- /* The %p output is implementation-defined. It's possible
- to determine this format but due to extensions (especially
- those of the Linux kernel -- see bug 78512) the first %p
- in the format string disables any further processing. */
- return false;
+ if (dump_file)
+ {
+ fprintf (dump_file, " Directive %u at offset %zu: \"%.*s\"",
+ dir.dirno, (size_t)(dir.beg - info.fmtstr),
+ (int)dir.len, dir.beg);
+ if (star_width)
+ fprintf (dump_file, ", width = %lli", (long long)dir.width);
- case 'n':
- /* %n has side-effects even when nothing is actually printed to
- any buffer. */
- info.nowrite = false;
- break;
+ if (star_precision)
+ fprintf (dump_file, ", precision = %lli", (long long)dir.prec);
- case 'c':
- case 'S':
- case 's':
- spec.fmtfunc = format_string;
- break;
+ fputc ('\n', dump_file);
+ }
- default:
- /* Unknown conversion specification. */
- return false;
- }
+ return dir.len;
+}
- 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. */
- size_t dirlen = pf - dir;
+bool
+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
- available (e.g., the function doesn't take a va_list). Treat
- missing arguments the same as va_list, even though they will
- have likely already been diagnosed by -Wformat. */
- tree arg = NULL_TREE;
- if (spec.specifier != '%'
- && argno < gimple_call_num_args (info.callstmt))
- arg = gimple_call_arg (info.callstmt, dollar ? dollar : argno++);
+ /* Return success the directive is zero bytes long and it's
+ the last think in the format string (i.e., it's the terminating
+ nul, which isn't really a directive but handling it as one makes
+ things simpler). */
+ if (!n)
+ return *pf == '\0';
- ::format_directive (info, res, dir, dirlen, spec, arg);
+ pf += n;
}
/* Complete format string was processed (with or without warnings). */