better handling of ranges (PR 78703)

Message ID f082cb5e-9f2c-cc78-5093-4a46d2304d3d@gmail.com
State New
Headers show

Commit Message

Martin Sebor Jan. 9, 2017, 4:18 p.m.
On 01/08/2017 09:44 PM, Jeff Law wrote:
> On 12/23/2016 02:25 PM, Martin Sebor wrote:

>> Bug 78703 points out that the decimal point character in floating

>> directives can be longer than just one byte (in locales where the

>> decimal point is a multibyte character).  The decimal point can

>> result in anywhere between 1 and MB_LEN_MAX bytes.  This is unlikely

>> but locales with two-byte decimal point are known to exist, and

>> the gimple-ssa-sprintf pass must handle them correctly.

>>

>> In a comment on the bug Jakub suggests that while printf return

>> value optimization must correctly deal with the worst case (i.e.,

>> MB_LEN_MAX of 6 for UTF-8), reflecting the worst case in the text

>> of warnings could be confusing to users most of whom expect

>> a single byte decimal point.

>>

>> Finally, a limitation of the gimple-ssa-sprintf pass has been that

>> it only understands constant width and precision and treats others

>> as essentially unlimited even if they are constrained to a limited

>> range of values.  This results in false positives and negatives

>> that can be avoided.

>>

>> The attached patch enhances the pass to overcome both of these

>> limitations.  It does that by first replacing the exact byte counter

>> with two other counters: 1) a likely counter that tracks the number

>> of bytes a directive is likely to result in, and 2) an "unlikely"

>> byte for lack of a better name, that tracks the unlikely maximum

>> byte count in cases like multibyte decimal point, and second by

>> adding range handling for width and precision specified by the

>> asterisk (such as in sprintf("%*.*i", w, p, i)).

>>

>> The patch resulted in more extensive changes than I initially

>> intended but the result is a simplified implementation.  A good

>> amount of the changes is factoring code out into more general

>> functions that can be shared throughout the pass.

>>

>> With these enhancements, although the support for ranges in the

>> pass is complete, it's not as robust as it could be.  I think

>> having the pass run later could improve things.

>>

>> The pass does produce a fair number of warnings for calls to

>> snprintf in the linux kernel.  Some of these I suspect will be

>> considered false positives.  I think it might be worth splitting

>> up the snprintf warning from -Wformat-length and adding a separate

>> option to control it.

>>

>> Martin

>>

>> gcc-78703.diff

>>

>>

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

>> incorrect in locales with a mulltibyte decimal point

>>

>> gcc/ChangeLog:

>>

>>     PR middle-end/78703

>>     * gimple-ssa-sprintf.c (get_int_range): New function.

>>     (struct result_range): Add members.

>>     (struct format_result): Replace number_chars, number_chars_min, and

>>     number_chars_max, with struct result_ramge.  Remove constant.

>>     (format_result::operator+=): Update and define out of class.

>>     (struct fmtresult): Add constructors.  Remove constant and bounded

>>     members.

>>     (format_result::type_max_digits): New function.

>>     (format_result::adjust_for_width_and_precision): New function.

>>     (struct conversion_spec): Rename...

>>     (struct directive): ...to this.

>>     (struct directive): Add new data members.

>>     (directive::set_width, directive::set_precison): New functions.

>>     (bytes_remaining, get_int_range, format_character, format_plain):

>> Same.

>>     (should_warn_p, maybe_warn, parse_directive): Same.

>>     (min_bytes_remaining, add_bytes): Remove.

>>     (format_percent, get_string_length): Simplify.

>>     (format_integer): Handle width and precision ranges.

>>     (format_floating): Same.

>>     (get_mpfr_format_length): Work around MPFR bugs and simplify.

>>     (format_string): Factor single character handling into

>>     format_character.  Handle width and precision ranges.

>>     (format_directive): Factor out most warning code into maybe_warn.

>>     (pass_sprintf_length::compute_format_length): Factor out parsing

>>     into parse_directive.

>>     (try_substitute_return_value): Handle unlikely maximum byte counter.

>>     Simplify for better clarity.

>>

>> gcc/testsuite/ChangeLog:

>>

>>     PR middle-end/78703

>>     * gcc.dg/tree-ssa/builtin-sprintf-2.c: Adjust.

>>     * gcc.dg/tree-ssa/builtin-sprintf-5.c: Same.

>>     * gcc.dg/tree-ssa/builtin-sprintf-warn-1.c: Same.

>>     * gcc.dg/tree-ssa/builtin-sprintf-warn-2.c: Same.

>>     * gcc.dg/tree-ssa/builtin-sprintf-warn-3.c: Same.

>>     * gcc.dg/tree-ssa/builtin-sprintf-warn-4.c: Same.

>>     * gcc.dg/tree-ssa/builtin-sprintf-warn-7.c: Same.

>>     * gcc.dg/tree-ssa/builtin-sprintf-warn-9.c: New test.

>>     * gcc.dg/tree-ssa/builtin-sprintf.c: Adjust.

>>     * gcc.dg/format/pr78569.c: Same.

>>

>

>> +  /* Get the real type format desription for the target.  */

> s/desription/description/

>

>

>

>

>> @@ -1613,24 +1738,23 @@ get_string_length (tree str)

>>

>>    if (lenrange [0] || lenrange [1])

>>      {

>> -      fmtresult res;

>> +      fmtresult res (tree_fits_uhwi_p (lenrange[0])

>> +             ? tree_to_uhwi (lenrange[0]) : 1 < warn_format_length,

> Ordering of operands.

>

>

>

> +      else if (0 < min && min < 128)

> Order of operands.  It would be helpful if you could just do an

> iteration over the patch for these issues.

>

>

>> +      if (0 <= dir.prec[1]

>> +          && (unsigned HOST_WIDE_INT)dir.prec[1] < res.range.max)

> Order of operands.

>

>> +      if (0 <= dir.prec[0])

> Order of operands.

>

>

>

>

>> -      if (res.range.max < (unsigned HOST_WIDE_INT)width)

>> -    res.range.max = width;

>> +  if (1 == warn_format_length

> Order of operands here.

>

>

>

>> @@ -1843,27 +2276,28 @@ format_string (const conversion_spec &spec,

>> tree arg)

>> @@ -1889,6 +2318,7 @@ format_directive (const

>> pass_sprintf_length::call_info &info,

>>       they refer to.  */

>>    res->knownrange &= fmtres.knownrange;

>>

>> +#if 1

> Presumably you should just get rid of the cpp directives.

>

>

>

>

>> -

>>    /* Compute the number of available bytes in the destination.  There

>>       must always be at least one byte of space for the terminating

>>       NUL that's appended after the format string has been processed.  */

>> -  unsigned HOST_WIDE_INT navail = min_bytes_remaining (info.objsize,

>> *res);

>> +  // unsigned HOST_WIDE_INT navail = min_bytes_remaining

>> (info.objsize, *res);

>> +  result_range avail_range = bytes_remaining (info.objsize, *res);

> Remove the commented out line.

>

>

>> +  /* Clear UNDER4K in the overall result if the maximum has exceeded

>> +     the 4k (this is necessary to avoid the return valuye optimization

> s/valuye/value/

>

>> +  if (!warned

>> +      /* Only warn at level 2.  */

>> +      && 1 < warn_format_length

> Order of operands.

>

>

>> +  else if ('*' == *pf)

> Order of operands.

>

>

> As much as I hate to say it, I think we need to find a way to break this

> down into something more manageable.  It's just impossible to see the

> structure of what you're doing with the implementation changes mixed in

> with what appear to be refactoring changes.

>

> Is there any way we can start to break this down into something more

> manageable?


I'm sorry it's too big.  I actually did start out by doing in stages
and and have a smaller initial patch that the rest of it builds on.
I unfortunately forgot that I had done that when I posted the bigger
patch and then left for my trip.

Attached are the two revisions. The description of the major changes
in each of the two is below.  The patches are bigger in part because
of the name change (conversion_spec to directive and spec to dir),
but also because the range work provided an opportunity to factor
out and consolidate a good amount of code.  IMO, the end result is
quite a bit cleaner than before and should be easier to follow.

I'm not sure how difficult or time consuming it would be to split
up the second of the two patches if it's still too big.  I suspect
it would take a few days.

gcc-78703-1.diff

   Extends struct conversion_spec to struct directive so that ordinary
   format characters can be handled uniformly with other directives
   (those that start with a '%') and adds format_plain to format them.

   It also adds parse_directive and moves directive parsing from
   pass_sprintf_length::compute_format_length to it.

   It makes no changes to the warning or optimization logic and so
   the test updates only to reflect the uniform directive handling.

gcc-78703-2.diff

   Contains the bulk of the rest of the changes.  I.e., it replaces
   the superfluous exact byte counter with the likely counter and
   adds the unlikely counter.

   It replaces the get_width_and_precision function with get_int_range
   to get ranges for width and precision ranges and adds
   fmtresult::adjust_for_width_or_precision to handle with and precision
   ranges.

   It adds tge should_warn_p function and moves all logic to determine
   whether or not to issue a warning at a given point in the processing
   of a format string to it.

   It adds the maybe_warn function and ymoves all the format directive
   overflow and truncation warnings into it.

   It removes the add_bytes function (its fully superseded by
   format_directive.

   Finally, this version also enhances the debugging output to include
   width and precision ranges.

Martin

Comments

Jeff Law Jan. 12, 2017, 9:44 p.m. | #1
On 01/09/2017 09:18 AM, Martin Sebor wrote:
> On 01/08/2017 09:44 PM, Jeff Law wrote:


>> As much as I hate to say it, I think we need to find a way to break this

>> down into something more manageable.  It's just impossible to see the

>> structure of what you're doing with the implementation changes mixed in

>> with what appear to be refactoring changes.

>>

>> Is there any way we can start to break this down into something more

>> manageable?

>

> I'm sorry it's too big.  I actually did start out by doing in stages

> and and have a smaller initial patch that the rest of it builds on.

> I unfortunately forgot that I had done that when I posted the bigger

> patch and then left for my trip.

>

> Attached are the two revisions. The description of the major changes

> in each of the two is below.  The patches are bigger in part because

> of the name change (conversion_spec to directive and spec to dir),

> but also because the range work provided an opportunity to factor

> out and consolidate a good amount of code.  IMO, the end result is

> quite a bit cleaner than before and should be easier to follow.

>

> I'm not sure how difficult or time consuming it would be to split

> up the second of the two patches if it's still too big.  I suspect

> it would take a few days.

>

> gcc-78703-1.diff

>

>   Extends struct conversion_spec to struct directive so that ordinary

>   format characters can be handled uniformly with other directives

>   (those that start with a '%') and adds format_plain to format them.

>

>   It also adds parse_directive and moves directive parsing from

>   pass_sprintf_length::compute_format_length to it.

>

>   It makes no changes to the warning or optimization logic and so

>   the test updates only to reflect the uniform directive handling.

>

> gcc-78703-2.diff

>

>   Contains the bulk of the rest of the changes.  I.e., it replaces

>   the superfluous exact byte counter with the likely counter and

>   adds the unlikely counter.

>

>   It replaces the get_width_and_precision function with get_int_range

>   to get ranges for width and precision ranges and adds

>   fmtresult::adjust_for_width_or_precision to handle with and precision

>   ranges.

>

>   It adds tge should_warn_p function and moves all logic to determine

>   whether or not to issue a warning at a given point in the processing

>   of a format string to it.

>

>   It adds the maybe_warn function and ymoves all the format directive

>   overflow and truncation warnings into it.

>

>   It removes the add_bytes function (its fully superseded by

>   format_directive.

>

>   Finally, this version also enhances the debugging output to include

>   width and precision ranges.

>

> Martin

>

> gcc-78703-1.diff

>

>

> commit 366503058ae7fa8ff689b258f728010d942e0354

> Author: Martin Sebor <msebor@redhat.com>

> Date:   Fri Dec 16 16:13:10 2016 -0700

>

>     Rename struct conversion_spec with struct directive.

>     Add format_plain to format plain format substrings.

>     Change format_directive to handle plain format substrings

>     and to print called function name in diagnostics

>     Add parse_directive.

>     Simplify pass_sprintf_length::compute_format_length and

>     call parse_directive.

>     Adjust tests.

>

> diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c

> index a91dcb8..22ef86b 100644

> --- a/gcc/gimple-ssa-sprintf.c

> +++ b/gcc/gimple-ssa-sprintf.c

> @@ -1838,32 +1848,42 @@ format_string (const conversion_spec &spec, tree arg)

>    return res;

>  }

>

> +

> +static fmtresult

> +format_plain (const directive &spec, tree)

Needs a trivial function comment.

   	  else if (navail < fmtres.range.max
> -		   && (((spec.specifier == 's'

> +		   && (((dir.specifier == 's'

>  			 && fmtres.range.max < HOST_WIDE_INT_MAX)

> -			/* && (spec.precision || spec.star_precision) */)

> +			/* && (dir.precision || dir.star_precision) */)

>  		       || 1 < warn_format_length))

Any good reason to keep the commented out code here?  If not, let's 
remove it.

With an updated changelog the -1 patch is OK.  It was a *lot* easier to 
see what you were doing here.

Jeff
Jeff Law Jan. 12, 2017, 10:31 p.m. | #2
On 01/09/2017 09:18 AM, Martin Sebor wrote:
> On 01/08/2017 09:44 PM, Jeff Law wrote:

>> On 12/23/2016 02:25 PM, Martin Sebor wrote:

>>> Bug 78703 points out that the decimal point character in floating

>>> directives can be longer than just one byte (in locales where the

>>> decimal point is a multibyte character).  The decimal point can

>>> result in anywhere between 1 and MB_LEN_MAX bytes.  This is unlikely

>>> but locales with two-byte decimal point are known to exist, and

>>> the gimple-ssa-sprintf pass must handle them correctly.

>>>

>>> In a comment on the bug Jakub suggests that while printf return

>>> value optimization must correctly deal with the worst case (i.e.,

>>> MB_LEN_MAX of 6 for UTF-8), reflecting the worst case in the text

>>> of warnings could be confusing to users most of whom expect

>>> a single byte decimal point.

>>>

>>> Finally, a limitation of the gimple-ssa-sprintf pass has been that

>>> it only understands constant width and precision and treats others

>>> as essentially unlimited even if they are constrained to a limited

>>> range of values.  This results in false positives and negatives

>>> that can be avoided.

>>>

>>> The attached patch enhances the pass to overcome both of these

>>> limitations.  It does that by first replacing the exact byte counter

>>> with two other counters: 1) a likely counter that tracks the number

>>> of bytes a directive is likely to result in, and 2) an "unlikely"

>>> byte for lack of a better name, that tracks the unlikely maximum

>>> byte count in cases like multibyte decimal point, and second by

>>> adding range handling for width and precision specified by the

>>> asterisk (such as in sprintf("%*.*i", w, p, i)).

>>>

>>> The patch resulted in more extensive changes than I initially

>>> intended but the result is a simplified implementation.  A good

>>> amount of the changes is factoring code out into more general

>>> functions that can be shared throughout the pass.

>>>

>>> With these enhancements, although the support for ranges in the

>>> pass is complete, it's not as robust as it could be.  I think

>>> having the pass run later could improve things.

>>>

>>> The pass does produce a fair number of warnings for calls to

>>> snprintf in the linux kernel.  Some of these I suspect will be

>>> considered false positives.  I think it might be worth splitting

>>> up the snprintf warning from -Wformat-length and adding a separate

>>> option to control it.

>>>

>>> Martin

>>>

>>> gcc-78703.diff

>>>

>>>

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

>>> incorrect in locales with a mulltibyte decimal point

>>>

>>> gcc/ChangeLog:

>>>

>>>     PR middle-end/78703

>>>     * gimple-ssa-sprintf.c (get_int_range): New function.

>>>     (struct result_range): Add members.

>>>     (struct format_result): Replace number_chars, number_chars_min, and

>>>     number_chars_max, with struct result_ramge.  Remove constant.

>>>     (format_result::operator+=): Update and define out of class.

>>>     (struct fmtresult): Add constructors.  Remove constant and bounded

>>>     members.

>>>     (format_result::type_max_digits): New function.

>>>     (format_result::adjust_for_width_and_precision): New function.

>>>     (struct conversion_spec): Rename...

>>>     (struct directive): ...to this.

>>>     (struct directive): Add new data members.

>>>     (directive::set_width, directive::set_precison): New functions.

>>>     (bytes_remaining, get_int_range, format_character, format_plain):

>>> Same.

>>>     (should_warn_p, maybe_warn, parse_directive): Same.

>>>     (min_bytes_remaining, add_bytes): Remove.

>>>     (format_percent, get_string_length): Simplify.

>>>     (format_integer): Handle width and precision ranges.

>>>     (format_floating): Same.

>>>     (get_mpfr_format_length): Work around MPFR bugs and simplify.

>>>     (format_string): Factor single character handling into

>>>     format_character.  Handle width and precision ranges.

>>>     (format_directive): Factor out most warning code into maybe_warn.

>>>     (pass_sprintf_length::compute_format_length): Factor out parsing

>>>     into parse_directive.

>>>     (try_substitute_return_value): Handle unlikely maximum byte counter.

>>>     Simplify for better clarity.

>>>

>>> gcc/testsuite/ChangeLog:

>>>

>>>     PR middle-end/78703

>>>     * gcc.dg/tree-ssa/builtin-sprintf-2.c: Adjust.

>>>     * gcc.dg/tree-ssa/builtin-sprintf-5.c: Same.

>>>     * gcc.dg/tree-ssa/builtin-sprintf-warn-1.c: Same.

>>>     * gcc.dg/tree-ssa/builtin-sprintf-warn-2.c: Same.

>>>     * gcc.dg/tree-ssa/builtin-sprintf-warn-3.c: Same.

>>>     * gcc.dg/tree-ssa/builtin-sprintf-warn-4.c: Same.

>>>     * gcc.dg/tree-ssa/builtin-sprintf-warn-7.c: Same.

>>>     * gcc.dg/tree-ssa/builtin-sprintf-warn-9.c: New test.

>>>     * gcc.dg/tree-ssa/builtin-sprintf.c: Adjust.

>>>     * gcc.dg/format/pr78569.c: Same.

>>>

>>

>>> +  /* Get the real type format desription for the target.  */

>> s/desription/description/

>>

>>

>>

>>

>>> @@ -1613,24 +1738,23 @@ get_string_length (tree str)

>>>

>>>    if (lenrange [0] || lenrange [1])

>>>      {

>>> -      fmtresult res;

>>> +      fmtresult res (tree_fits_uhwi_p (lenrange[0])

>>> +             ? tree_to_uhwi (lenrange[0]) : 1 < warn_format_length,

>> Ordering of operands.

>>

>>

>>

>> +      else if (0 < min && min < 128)

>> Order of operands.  It would be helpful if you could just do an

>> iteration over the patch for these issues.

>>

>>

>>> +      if (0 <= dir.prec[1]

>>> +          && (unsigned HOST_WIDE_INT)dir.prec[1] < res.range.max)

>> Order of operands.

>>

>>> +      if (0 <= dir.prec[0])

>> Order of operands.

>>

>>

>>

>>

>>> -      if (res.range.max < (unsigned HOST_WIDE_INT)width)

>>> -    res.range.max = width;

>>> +  if (1 == warn_format_length

>> Order of operands here.

>>

>>

>>

>>> @@ -1843,27 +2276,28 @@ format_string (const conversion_spec &spec,

>>> tree arg)

>>> @@ -1889,6 +2318,7 @@ format_directive (const

>>> pass_sprintf_length::call_info &info,

>>>       they refer to.  */

>>>    res->knownrange &= fmtres.knownrange;

>>>

>>> +#if 1

>> Presumably you should just get rid of the cpp directives.

>>

>>

>>

>>

>>> -

>>>    /* Compute the number of available bytes in the destination.  There

>>>       must always be at least one byte of space for the terminating

>>>       NUL that's appended after the format string has been

>>> processed.  */

>>> -  unsigned HOST_WIDE_INT navail = min_bytes_remaining (info.objsize,

>>> *res);

>>> +  // unsigned HOST_WIDE_INT navail = min_bytes_remaining

>>> (info.objsize, *res);

>>> +  result_range avail_range = bytes_remaining (info.objsize, *res);

>> Remove the commented out line.

>>

>>

>>> +  /* Clear UNDER4K in the overall result if the maximum has exceeded

>>> +     the 4k (this is necessary to avoid the return valuye optimization

>> s/valuye/value/

>>

>>> +  if (!warned

>>> +      /* Only warn at level 2.  */

>>> +      && 1 < warn_format_length

>> Order of operands.

>>

>>

>>> +  else if ('*' == *pf)

>> Order of operands.

>>

>>

>> As much as I hate to say it, I think we need to find a way to break this

>> down into something more manageable.  It's just impossible to see the

>> structure of what you're doing with the implementation changes mixed in

>> with what appear to be refactoring changes.

>>

>> Is there any way we can start to break this down into something more

>> manageable?

>

> I'm sorry it's too big.  I actually did start out by doing in stages

> and and have a smaller initial patch that the rest of it builds on.

> I unfortunately forgot that I had done that when I posted the bigger

> patch and then left for my trip.

>

> Attached are the two revisions. The description of the major changes

> in each of the two is below.  The patches are bigger in part because

> of the name change (conversion_spec to directive and spec to dir),

> but also because the range work provided an opportunity to factor

> out and consolidate a good amount of code.  IMO, the end result is

> quite a bit cleaner than before and should be easier to follow.

>

> I'm not sure how difficult or time consuming it would be to split

> up the second of the two patches if it's still too big.  I suspect

> it would take a few days.

>

> gcc-78703-1.diff

>

>   Extends struct conversion_spec to struct directive so that ordinary

>   format characters can be handled uniformly with other directives

>   (those that start with a '%') and adds format_plain to format them.

>

>   It also adds parse_directive and moves directive parsing from

>   pass_sprintf_length::compute_format_length to it.

>

>   It makes no changes to the warning or optimization logic and so

>   the test updates only to reflect the uniform directive handling.

>

> gcc-78703-2.diff

>

>   Contains the bulk of the rest of the changes.  I.e., it replaces

>   the superfluous exact byte counter with the likely counter and

>   adds the unlikely counter.

>

>   It replaces the get_width_and_precision function with get_int_range

>   to get ranges for width and precision ranges and adds

>   fmtresult::adjust_for_width_or_precision to handle with and precision

>   ranges.

>

>   It adds tge should_warn_p function and moves all logic to determine

>   whether or not to issue a warning at a given point in the processing

>   of a format string to it.

>

>   It adds the maybe_warn function and ymoves all the format directive

>   overflow and truncation warnings into it.

>

>   It removes the add_bytes function (its fully superseded by

>   format_directive.

>

>   Finally, this version also enhances the debugging output to include

>   width and precision ranges.

I'm still having trouble wrapping my head around 78703-2.  It's just huge.

So the most obvious thing I see to break out is the should_warn_p 
changes.  If maybe_warn could be a distinct patch too, that would help. 
  It may not seem like much, but it really does help.  There may be more 
that sticks out after that.



jeff
Martin Sebor Jan. 16, 2017, 1:12 a.m. | #3
> I'm still having trouble wrapping my head around 78703-2.  It's just huge.

>

> So the most obvious thing I see to break out is the should_warn_p

> changes.  If maybe_warn could be a distinct patch too, that would help.

>  It may not seem like much, but it really does help.  There may be more

> that sticks out after that.


As I mentioned in private, I don't think I can break up the patch
before stage 3 closes.  I spent pretty much the whole weekend
integrating it with the changes made on trunk since I started
working on it.  It's a very time consuming process mainly due
to the large number of tests and the amount of detail in the
diagnostics they are written to verify.

In hopes of helping with the review I've annotated the second part
of the patch with detailed comments explaining each hunk (look for
@@ at the beginning of the line).  Since the patch causes many
conflicts with the top of trunk I attach the file itself.  Please
let me know if there's something else I can do to make this review
less of a burden.

Martindiff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c
index 22ef86b..0c804f4 100644
--- a/gcc/gimple-ssa-sprintf.c
+++ b/gcc/gimple-ssa-sprintf.c
@@ -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
    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
@@ -92,6 +92,10 @@ along with GCC; see the file COPYING3.  If not see
 
 namespace {
 
+static bool
+get_int_range (tree, tree, HOST_WIDE_INT *, HOST_WIDE_INT *,
+	       bool, HOST_WIDE_INT);
+
 const pass_data pass_data_sprintf_length = {
   GIMPLE_PASS,             // pass type
   "printf-return-value",   // pass name
@@ -145,2 +149,2 @@ pass_sprintf_length::gate (function *)
 	  && (optimize > 0) == fold_return_value);
 }

@@ Introduce likely and ulikely byte counters.  Likely replaces the exact
@@ counter (which was superfluous because it had the same value as min
@@ and max when they were equal).

+/* The minimum, maximum, likely, and unlikely maximum number of bytes
+   of output either a formatting function or an individual directive
+   can result in.  */
+
+struct result_range
+{
+  /* The absolute minimum number of bytes.  The result of a successful
+     conversion is guaranteed to be no less than this.  (An erroneous
+     conversion can be indicated by MIN > HOST_WIDE_INT_MAX.)  */
+  unsigned HOST_WIDE_INT min;
+  /* The likely maximum result that is used in diagnostics.  In most
+     cases MAX is the same as the worst case UNLIKELY result.  */
+  unsigned HOST_WIDE_INT max;
+  /* The likely result used to trigger diagnostics.  For conversions
+     that result in a range of bytes [MIN, MAX], LIKELY is somewhere
+     in that range.  */
+  unsigned HOST_WIDE_INT likely;
+  /* In rare cases (e.g., for nultibyte characters) UNLIKELY gives
+     the worst cases maximum result of a directive.  In most cases
+     UNLIKELY == MAX.  UNLIKELY is used to control the return value
+     optimization but not in diagnostics.  */
+  unsigned HOST_WIDE_INT unlikely;
+};
+
 /* The result of a call to a formatted function.  */
 
 struct format_result
 {

@@ Remove counters and replace them with result_range defined above.

-  /* Number of characters written by the formatted function, exact,
-     minimum and maximum when an exact number cannot be determined.
-     Setting the minimum to HOST_WIDE_INT_MAX disables all length
-     tracking for the remainder of the format string.
-     Setting either of the other two members to HOST_WIDE_INT_MAX
-     disables the exact or maximum length tracking, respectively,
-     but continues to track the maximum.  */
-  unsigned HOST_WIDE_INT number_chars;
-  unsigned HOST_WIDE_INT number_chars_min;
-  unsigned HOST_WIDE_INT number_chars_max;
-
-  /* True when the range given by NUMBER_CHARS_MIN and NUMBER_CHARS_MAX
-     can be relied on for value range propagation, false otherwise.
-     This means that BOUNDED must not be set if the number of bytes
-     produced by any directive is unspecified or implementation-
-     defined (unless the implementation's behavior is known and
-     determined via a target hook).
-     Note that BOUNDED only implies that the length of a function's
-     output is known to be within some range, not that it's constant
-     and a candidate for string folding.  BOUNDED is a stronger
-     guarantee than KNOWNRANGE.  */
-  bool bounded;
+  /* Range of characters written by the formatted function.
+     Setting the minimum to HOST_WIDE_INT_MAX disables all
+     length tracking for the remainder of the format string.  */
+  result_range range;
 
   /* True when the range above is obtained from known values of
-     directive arguments or their bounds and not the result of
-     heuristics that depend on warning levels.  It is used to
-     issue stricter diagnostics in cases where strings of unknown
-     lengths are bounded by the arrays they are determined to
-     refer to.  KNOWNRANGE must not be used to set the range of
-     the return value of a call.  */
+     directive arguments, or bounds on the amount of output such
+     as width and precision, and not the result of  heuristics that
+     depend on warning levels.  It's used to issue stricter diagnostics
+     in cases where strings of unknown lengths are bounded by the arrays
+     they are determined to refer to.  KNOWNRANGE must not be used for
+     the return value optimization.  */
   bool knownrange;

@ Eliminate the CONSTANT member for simplicity (it wasn't used for
@ anything).

-  /* True when the output of the formatted call is constant (and
-     thus a candidate for string constant folding).  This is rare
-     and typically requires that the arguments of all directives
-     are also constant.  CONSTANT implies BOUNDED.  */
-  bool constant;
-
   /* True if no individual directive resulted in more than 4095 bytes
-     of output (the total NUMBER_CHARS might be greater).  */
+     of output (the total NUMBER_CHARS_{MIN,MAX} might be greater).
+     Implementations are not required to handle directives that produce
+     more than 4K bytes (leading to undefined behavior) and so when one
+     is found it disables the return value optimization.  */
   bool under4k;
 
   /* True when a floating point directive has been seen in the format
@@ -197,7 +204,7 @@ struct format_result
 
   /* True when an intermediate result has caused a warning.  Used to
      avoid issuing duplicate warnings while finishing the processing
-     of a call.  */
+     of a call.  WARNED also disables the return value optimization.  */
   bool warned;
 
   /* Preincrement the number of output characters by 1.  */
@@ -215,20 +222,29 @@ struct format_result
@@ Adjust to handle the new byte counters.
   }

@ Update the operator to handle LIKELY/UNLIKELY counters.

   /* 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 (range.min < HOST_WIDE_INT_MAX)
+    range.min += n;
+
+  if (range.max < HOST_WIDE_INT_MAX)
+    range.max += n;
+
+  if (range.likely < HOST_WIDE_INT_MAX)
+    range.likely += n;
+
+  if (range.unlikely < HOST_WIDE_INT_MAX)
+    range.unlikely += n;
+
+  return *this;
+}
+
 /* Return the value of INT_MIN for the target.  */
 
 static inline HOST_WIDE_INT
@@ -426,25 +442,45 @@ enum format_lengths
 };
 
@ Moved before struct FORMAT_RESULT that uses it.

-/* A minimum and maximum number of bytes.  */
-
-struct result_range
-{
-  unsigned HOST_WIDE_INT min, max;
-};
-
 /* Description of the result of conversion either of a single directive
    or the whole format string.  */
 
@@ Add ctors to struct FMTRESULT to avoid repeating the same assignments.
@@ Mostly for convenience and to avoid forgetting to set some members.

 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),
+    nullp ()
+  {
+    range.min = min;
+    range.max = min;
+    range.likely = min;
+    range.unlikely = min;
+  }
+
+  /* Construct a FMTRESULT object with MIN, MAX, and LIKELY counters.
+     KNOWNRANGE is set when both MIN and MAX are valid.   */
+  fmtresult (unsigned HOST_WIDE_INT min, unsigned HOST_WIDE_INT max,
+	     unsigned HOST_WIDE_INT likely = HOST_WIDE_INT_MAX)
+  : argmin (), argmax (),
+    knownrange (min < HOST_WIDE_INT_MAX && max < HOST_WIDE_INT_MAX),
+    nullp ()
   {
-    range.min = range.max = HOST_WIDE_INT_MAX;
+    range.min = min;
+    range.max = max;
+    range.likely = max < likely ? min : likely;
+    range.unlikely = max;
   }
 
-  /* The range a directive's argument is in.  */
+/* Adjust result upward to reflect the RANGE of values the specified
+   width or precision is known to be in.  */
+  fmtresult& adjust_for_width_or_precision (const HOST_WIDE_INT[2],
+					    tree = NULL_TREE,
+					    unsigned = 0, unsigned = 0);
+
+ /* The range a directive's argument is in.  */
   tree argmin, argmax;
 
   /* The minimum and maximum number of bytes that a directive
@@ -456,2 +492,2 @@ struct fmtresult
      heuristics that depend on warning levels.  */
   bool knownrange;

@@ Remove BOUNDED.  It became unnecessary (it can be determined from
@@ the byte counters).
-  /* True when the range is the result of an argument determined
-     to be bounded to a subrange of its type or value (such as by
-     value range propagation or the width of the formt directive),
-     false otherwise.  */
-  bool bounded;
-

@@ Remove CONSTANT.  It wasn't used for anything.
-  /* True when the output of a directive is constant.  This is rare
-     and typically requires that the argument(s) of the directive
-     are also constant (such as determined by constant propagation,
-     though not value range propagation).  */
-  bool constant;
-
   /* True when the argument is a null pointer.  */
   bool nullp;
+
+  /* Return the maximum number of decimal digits a value of TYPE
+     formats as on output.  */
+  static unsigned type_max_digits (tree, int);
 };
 
-/* Description of a format directive.  */

@@ Function called from directive for all directives that accept width
@@ or precision.  Avoids duplicating the same code in all of them.

+/* Adjust result upward to reflect the range ADJUST of values the
+   specified width or precision is known to be in.  When non-null,
+   TYPE denotes the type of the directive whose result is being
+   adjusted, BASE gives the base of the directive (octal, decimal,
+   or hex), and ADJ denotes the additional adjustment to the LIKELY
+   counter that may need to be added when ADJUST is a range.  */
+
+fmtresult&
+fmtresult::adjust_for_width_or_precision (const HOST_WIDE_INT adjust[2],
+					  tree type /* = NULL_TREE */,
+					  unsigned base /* = 0 */,
+					  unsigned adj /* = 0 */)
+{
+  bool minadjusted = false;
+
+  /* Adjust the minimum and likely counters.  */
+  if (0 <= adjust[0])
+    {
+      if (range.min < (unsigned HOST_WIDE_INT)adjust[0])
+	{
+	  range.min = adjust[0];
+	  minadjusted = true;
+	}
+
+      /* Adjust the likely counter.  */
+      if (range.likely < range.min)
+	range.likely = range.min;
+    }
+  else if (adjust[0] == target_int_min ()
+	   && (unsigned HOST_WIDE_INT)adjust[1] == target_int_max ())
+    {
+      knownrange = false;
+    }
+
+  /* Adjust the maximum counter.  */
+  if (0 < adjust[1])
+    {
+      if (range.max < (unsigned HOST_WIDE_INT)adjust[1])
+	{
+	  range.max = adjust[1];
+
+	  /* Set KNOWNRANGE if both the minimum and maximum have been
+	     adjusted.  Otherwise leave it at what it was before.  */
+	  knownrange = minadjusted;
+	}
+    }
+
+  if (warn_format_length > 1 && type)
+    {

@@ See type_max_digits just below.

+      /* For large non-constant width or precision whose range spans
+	 the maximum number of digits produced by the directive for
+	 any argument, set the likely number of bytes to be at most
+	 the number digits plus other adjustment determined by the
+	 caller (one for sign or two for the hexadecimal "0x"
+	 prefix).  */
+      unsigned dirdigs = type_max_digits (type, base);
+      if (adjust[0] < dirdigs && dirdigs < adjust[1]
+	  && range.likely < dirdigs)
+	range.likely = dirdigs + adj;
+    }
+  else if (range.likely < (range.min ? range.min : 1))
+    {
+      /* Conservatively, set LIKELY to at least MIN but no less than
+	 1 unless MAX is zero.  */
+      range.likely = (range.min
+		      ? range.min
+		      : range.max ? 1 : 0);
+    }
+
+  /* Finally adjust the unlikely counter to be at least as large as
+     the maximum.  */
+  if (range.unlikely < range.max)
+    range.unlikely = range.max;
+
+  return *this;
+}
+

@@ Helper to avoid warnings for width and precision ranges with
@@ exceedingly large upper bounds (likely due to their values being
@@ indeterminate).

+/* Return the maximum number of digits a value of TYPE formats in
+   BASE on output, not counting base prefix .  */
+
+unsigned
+fmtresult::type_max_digits (tree type, int base)
+{
+  unsigned prec = TYPE_PRECISION (type);
+  if (base == 8)
+    return (prec + 2) / 3;
+
+  if (base == 16)
+    return prec / 4;
+
+  /* Decimal approximation: yields 3, 5, 10, and 20 for precision
+     of 8, 16, 32, and 64 bits.  */
+  return prec * 301 / 1000 + 1;
+}
+
+/* Description of a format directive.  A directive is either a plain
+   string or a conversion specification that starts with '%'.  */

 struct directive
 {
@@ New member used in debugging dumps to help identify each directive
@@ in a format string.

+  /* The 1-based directive number (for debugging). */
+  unsigned dirno;
+
   /* The first character of the directive and its length.  */
   const char *beg;
   size_t len;
@@ -483,3 +610,3 @@ struct directive
   /* A bitmap of flags, one for each character.  */
   unsigned flags[256 / sizeof (int)];
 
@@ Replace WIDTH and PRECISION with arrays to represent the range of each.
@@ Get rid of STAR_WIDTH and STAR_PRECISION to avoid code duplication.
@@ Instead, parse_directive sets the range once for all directives.
-  /* Numeric width as in "%8x".  */
-  int width;
-
-  /* Numeric precision as in "%.32s".  */
-  int precision;
-
-  /* Width specified via the '*' character.  */
-  tree star_width;
-  /* Precision specified via the asterisk.  */
-  tree star_precision;
+  /* The range of values of the specified width, or -1 if not specified.  */
+  HOST_WIDE_INT width[2];
+  /* The range of values of the specified precision, or -1 if not
+     specified.  */
+  HOST_WIDE_INT prec[2];
 
   /* Length modifier.  */
   format_lengths modifier;
@@ -500,2 +622,2 @@ struct directive
   /* Format specifier character.  */
   char specifier;

@@ Remove unnecessary members (see STAR_{WIDTH,PRECISION} above).
-  /* 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;
@@ -540,3 +653,3 @@ struct directive
     flags[c / (CHAR_BIT * sizeof *flags)]
       &= ~(1U << (c % (CHAR_BIT * sizeof *flags)));
   }

@@ Introduce simple helpers to set either an exact WIDTH/PRECISION
@@ or range.
+
+  /* Set both bounds of the width range to VAL.  */
+  void set_width (HOST_WIDE_INT val)
+  {
+    width[0] = width[1] = val;
+  }
+
+  /* Set the width range according to ARG.  */
+  void set_width (tree arg)
+  {
+    get_int_range (arg, integer_type_node, width, width + 1, true, 0);
+  }
+
+  /* Set both bounds of the precision range to VAL.  */
+  void set_precision (HOST_WIDE_INT val)
+  {
+    prec[0] = prec[1] = val;
+  }
+
+  /* Set the precision range according to ARG.  */
+  void set_precision (tree arg)
+  {
+    get_int_range (arg, integer_type_node, prec, prec + 1, false, -1);
+  }
 };
 
 /* Return the logarithm of X in BASE.  */
@@ -613,7 +750,7 @@ tree_digits (tree x, int base, bool plus, bool prefix)
 }
 
 /* Given the formatting result described by RES and NAVAIL, the number
-   of available in the destination, return the number of bytes remaining
+   of available in the destination, return the range of bytes remaining
    in the destination.  */
 
 static inline result_range
@@ -623,0 +760,0 @@ bytes_remaining (unsigned HOST_WIDE_INT navail, const format_result &res)

@@ Handle LIKELY/UNLIKELY counters and simplify.

   if (HOST_WIDE_INT_MAX <= navail)
     {
-      range.min = range.max = navail;
+      range.min = range.max = range.likely = range.unlikely = navail;
       return range;
     }
 
-  if (res.number_chars < navail)
-    {
-      range.min = range.max = navail - res.number_chars;
-    }
-  else if (res.number_chars_min < navail)
-    {
-      range.max = navail - res.number_chars_min;
-    }
-  else
-    range.max = 0;
-
-  if (res.number_chars_max < navail)
-    range.min = navail - res.number_chars_max;
-  else
-    range.min = 0;
+  /* The lower bound of the available range is the available size
+     minus the maximum output size, and the upper bound is the size
+     minus the minimum.  */
+  range.max = res.range.min < navail ? navail - res.range.min : 0;
 
-  return range;
-}
-
-/* Given the formatting result described by RES and NAVAIL, the number
-   of available in the destination, return the minimum number of bytes
-   remaining in the destination.  */
-
-static inline unsigned HOST_WIDE_INT
-min_bytes_remaining (unsigned HOST_WIDE_INT navail, const format_result &res)
-{
-  if (HOST_WIDE_INT_MAX <= navail)
-    return navail;
+  range.likely = res.range.likely < navail ? navail - res.range.likely : 0;
 
-  if (1 < warn_format_length || res.knownrange)
-    {
-      /* At level 2, or when all directives output an exact number
-	 of bytes or when their arguments were bounded by known
-	 ranges, use the greater of the two byte counters if it's
-	 valid to compute the result.  */
-      if (res.number_chars_max < HOST_WIDE_INT_MAX)
-	navail -= res.number_chars_max;
-      else if (res.number_chars < HOST_WIDE_INT_MAX)
-	navail -= res.number_chars;
-      else if (res.number_chars_min < HOST_WIDE_INT_MAX)
-	navail -= res.number_chars_min;
-    }
+  if (res.range.max < HOST_WIDE_INT_MAX)
+    range.min = res.range.max < navail ? navail - res.range.max : 0;
   else
-    {
-      /* At level 1 use the smaller of the byte counters to compute
-	 the result.  */
-      if (res.number_chars < HOST_WIDE_INT_MAX)
-	navail -= res.number_chars;
-      else if (res.number_chars_min < HOST_WIDE_INT_MAX)
-	navail -= res.number_chars_min;
-      else if (res.number_chars_max < HOST_WIDE_INT_MAX)
-	navail -= res.number_chars_max;
-    }
+    range.min = range.likely;
 
-  if (navail > HOST_WIDE_INT_MAX)
-    navail = 0;
+  range.unlikely = (res.range.unlikely < navail
+		    ? navail - res.range.unlikely : 0);
 
-  return navail;
+  return range;
 }
 
 /* Description of a call to a formatted function.  */
@@ -729,0 +824,0 @@ struct pass_sprintf_length::call_info

@@ Make use of struct FMTRESULT ctor.

static fmtresult
 format_percent (const directive &, tree)
 {
-  fmtresult res;
-  res.argmin = res.argmax = NULL_TREE;
-  res.range.min = res.range.max = 1;
-  res.bounded = res.knownrange = res.constant = true;
+  fmtresult res (1);
   return res;
 }
 
@@ -777,8 +869,3 @@ 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.  */

@@ Introduce a new helper function to handle width and precision ranges
@@ and make it possible to set the bounds of each as expected: width in
@@ [0, -INT_MIN] and precision in [-1, INT_MAX].  ABSOLUTE determines
@@ which and NEGBOUND specifies the value of the lower bound.

+/* Determine the range [*PMIN, *PMAX] that the expression ARG of TYPE
+   is in.  Return true when the range is a subrange of that of TYPE.
+   Whn ARG is null it is as if it had the full range of TYPE.
+   When ABSOLUTE is true the range reflects the absolute value of
+   the argument.  When ABSOLUTE is false, negative bounds of
+   the determined range are replaced with NEGBOUND.  */
 
-static void
-get_width_and_precision (const directive &spec,
-			 HOST_WIDE_INT *pwidth, HOST_WIDE_INT *pprec)
+static bool
+get_int_range (tree arg, tree type, HOST_WIDE_INT *pmin, HOST_WIDE_INT *pmax,
+	       bool absolute, HOST_WIDE_INT negbound)
 {
-  HOST_WIDE_INT width = spec.have_width ? spec.width : 0;
-  HOST_WIDE_INT prec = spec.have_precision ? spec.precision : -1;
+  bool knownrange = false;
 
-  if (spec.star_width)
+  if (!arg)
+    {
+      *pmin = (TYPE_UNSIGNED (type)
+	       ? tree_to_uhwi (TYPE_MIN_VALUE (type))
+	       : tree_to_shwi (TYPE_MIN_VALUE (type)));
+      *pmax = tree_to_uhwi (TYPE_MAX_VALUE (type));
+    }
+  else if (TREE_CODE (arg) == INTEGER_CST)
     {
-      if (TREE_CODE (spec.star_width) == INTEGER_CST)
+      /* For a constant argument return its value adjusted as specified
+	 by NEGATIVE and NEGBOUND and return true to indicate that the
+	 result is known.  */
+      *pmin = tree_fits_shwi_p (arg) ? tree_to_shwi (arg) : tree_to_uhwi (arg);
+      *pmax = *pmin;
+      knownrange = true;
+    }
+  else
+    {
+      /* True if the argument's range cannot be determined.  */
+      bool unknown = true;
+
+      type = TREE_TYPE (arg);
+
+      if (TREE_CODE (arg) == SSA_NAME
+	  && TREE_CODE (type) == INTEGER_TYPE)
 	{
-	  width = tree_to_shwi (spec.star_width);
-	  if (width < 0)
+	  /* Try to determine the range of values of the integer argument.  */
+	  wide_int min, max;
+	  enum value_range_type range_type = get_range_info (arg, &min, &max);
+	  if (range_type == VR_RANGE)
 	    {
-	      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;
+	      HOST_WIDE_INT type_min
+		= (TYPE_UNSIGNED (type)
+		   ? tree_to_uhwi (TYPE_MIN_VALUE (type))
+		   : tree_to_shwi (TYPE_MIN_VALUE (type)));
+
+	      HOST_WIDE_INT type_max = tree_to_uhwi (TYPE_MAX_VALUE (type));
+
+	      *pmin = min.to_shwi ();
+	      *pmax = max.to_shwi ();
+
+	      /* Return true if the adjusted range is a subrange of
+		 the full range of the argument's type.  */
+	      knownrange = type_min < *pmin || *pmax < type_max;
+
+	      unknown = false;
 	    }
 	}
-      else
-	width = HOST_WIDE_INT_MIN;
+
+      /* Handle an argument with an unknown range as if none had been
+	 provided.  */
+      if (unknown)
+	return get_int_range (NULL_TREE, type, pmin, pmax, absolute, negbound);
     }
 
-  if (spec.star_precision)
+  /* Adjust each bound as specified by ABSOLUTE and NEGBOUND.  */
+  if (absolute)
     {
-      if (TREE_CODE (spec.star_precision) == INTEGER_CST)
+      if (*pmin < 0)
 	{
-	  prec = tree_to_shwi (spec.star_precision);
-	  if (prec < 0)
-	    prec = -1;
+	  if (*pmin == *pmax)
+	    *pmin = *pmax = -*pmin;
+	  else
+	    {
+	      HOST_WIDE_INT tmp = -*pmin;
+	      *pmin = 0;
+	      if (*pmax < tmp)
+		*pmax = tmp;
+	    }
 	}
-      else
-	prec = HOST_WIDE_INT_MIN;
     }
+  else if (*pmin < negbound)
+    *pmin = negbound;
 
-  *pwidth = width;
-  *pprec = prec;
+  return knownrange;
 }
 
 /* With the range [*ARGMIN, *ARGMAX] of an integer directive's actual
@@ -899,7 +1028,7 @@ adjust_range_for_overflow (tree dirtype, tree *argmin, tree *argmax)
    functions).  */
 
 static fmtresult
-format_integer (const directive &spec, tree arg)
+format_integer (const directive &dir, tree arg)
 {
   tree intmax_type_node;
   tree uintmax_type_node;

@@ Calling get_width_and_precision is no longer necessary here.  Both
@@ get set in parse_directive.

-  /* Set WIDTH and PRECISION based on the specification.  */
-  HOST_WIDE_INT width;
-  HOST_WIDE_INT prec;
-  get_width_and_precision (spec, &width, &prec);
+  /* Base to format the number in.  */
+  int base;
 
-  bool sign = spec.specifier == 'd' || spec.specifier == 'i';

@@ Move this block here from one of the conditionals below to give
@@ unconditional access to BASE.

+  /* True when a conversion is preceded by a prefix indicating the base
+     of the argument (octal or hexadecimal).  */
+  bool maybebase = dir.get_flag ('#');
+
+  /* True when a signed conversion is preceded by a sign or space.  */
+  bool maybesign = false;
+
+  /* True for signed conversions (i.e., 'd' and 'i').  */
+  bool sign = false;
+
+  switch (dir.specifier)
+    {
+    case 'd':
+    case 'i':
+      /* Space and '+' are  only meaningful for signed conversions.  */
+      maybesign = dir.get_flag (' ') | dir.get_flag ('+');
+      sign = true;
+      base = 10;
+      break;
+    case 'u':
+      base = 10;
+      break;
+    case 'o':
+      base = 8;
+      break;
+    case 'X':
+    case 'x':
+      base = 16;
+      break;
+    default:
+      gcc_unreachable ();
+    }
 
   /* 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;
@@ -979,3 +1137,4 @@ format_integer (const directive &spec, tree arg)
     {
       /* When a constant argument has been provided use its value
 	 rather than type to determine the length of the output.  */
+      fmtresult res;

@@ Block moved up.

-      /* Base to format the number in.  */
-      int base;
-
-      /* True when a signed conversion is preceded by a sign or space.  */
-      bool maybesign = false;
-
-      switch (spec.specifier)
-	{
-	case 'd':
-	case 'i':
-	  /* Space and '+' are  only meaningful for signed conversions.  */
-	  maybesign = spec.get_flag (' ') | spec.get_flag ('+');
-	  base = 10;
-	  break;
-	case 'u':
-	  base = 10;
-	  break;
-	case 'o':
-	  base = 8;
-	  break;
-	case 'X':
-	case 'x':
-	  base = 16;
-	  break;
-	default:
-	  gcc_unreachable ();
-	}
-
-      HOST_WIDE_INT len;
-
-      if ((prec == HOST_WIDE_INT_MIN || prec == 0) && integer_zerop (arg))
+      if ((dir.prec[0] <= 0 && dir.prec[1] >= 0) && integer_zerop (arg))
 	{
 	  /* As a special case, a precision of zero with a zero argument
 	     results in zero bytes except in base 8 when the '#' flag is
@@ -1019,4 +1148,3 @@ format_integer (const directive &spec, tree arg)
 	     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);

@@ BASE is still needed here (but also later outside this conditional).
@@ Set the LIKELY counter.

+	  res.range.min = ((base == 8 && dir.get_flag ('#')) || maybesign);
+	  if (res.range.min == 0 && dir.prec[0] != dir.prec[1])
+	    {
+	      res.range.max = 1;
+	      res.range.likely = 1;
+	    }
+	  else
+	    {
+	      res.range.max = res.range.min;
+	      res.range.likely = res.range.min;
+	    }
 	}
       else
 	{
 	  /* Convert the argument to the type of the directive.  */
 	  arg = fold_convert (dirtype, arg);
 
-	  /* True when a conversion is preceded by a prefix indicating the base
-	     of the argument (octal or hexadecimal).  */
-	  bool maybebase = spec.get_flag ('#');
-	  len = tree_digits (arg, base, maybesign, maybebase);
-
-	  if (len < prec)
-	    len = prec;
+	  res.range.min = tree_digits (arg, base, maybesign, maybebase);
+	  res.range.max = res.range.min;	
+	  res.range.likely = res.range.min;
 	}
@@ Replace adjusting counters for WIDTH and PRECISION...
-      if (len < width)
-	len = width;
-
-      /* The minimum and maximum number of bytes produced by the directive.  */
-      fmtresult res;
-
-      res.range.min = len;
-
-      /* The upper bound of the number of bytes is unlimited when either
-	 width or precision is specified but its value is unknown, and
-	 the same as the lower bound otherwise.  */
-      if (width == HOST_WIDE_INT_MIN || prec == HOST_WIDE_INT_MIN)
-	{
-	  res.range.max = HOST_WIDE_INT_MAX;
-	}
-      else
-	{
-	  res.range.max = len;
-	  res.bounded = true;
-	  res.constant = true;
-	  res.knownrange = true;
-	  res.bounded = true;
-	}
+      res.range.unlikely = res.range.max;

@@ ...with calls to the new helper.

+      /* Bump up the counters if WIDTH is greater than LEN.  */
+      res.adjust_for_width_or_precision (dir.width, dirtype, base,
+					 (sign | maybebase) + (base == 16));
+      /* Bump up the counters again if PRECision is greater still.  */
+      res.adjust_for_width_or_precision (dir.prec, dirtype, base,
+					 (sign | maybebase) + (base == 16));
       return res;
     }
   else if (TREE_CODE (TREE_TYPE (arg)) == INTEGER_TYPE
@@ -1073,2 +1192,2 @@ format_integer (const directive &spec, tree arg)
 
   fmtresult res;

@@ BOUNDED was eliminated.

-  /* The result is bounded unless width or precision has been specified
-     whose value is unknown.  */
-  res.bounded = width != HOST_WIDE_INT_MIN && prec != HOST_WIDE_INT_MIN;
-
   /* Using either the range the non-constant argument is in, or its
      type (either "formal" or actual), create a range of values that
      constrain the length of output given the warning level.  */
@@ -1124,7 +1239,7 @@ format_integer (const directive &spec, tree arg)
 	      if (code == INTEGER_CST)
 		{
 		  arg = gimple_assign_rhs1 (def);
-		  return format_integer (spec, arg);
+		  return format_integer (dir, arg);
 		}
 
 	      if (code == NOP_EXPR)
@@ -1144,3 +1259,3 @@ format_integer (const directive &spec, tree arg)
 	 or one whose value range cannot be determined, create a T_MIN
 	 constant if the argument's type is signed and T_MAX otherwise,
 	 and use those to compute the range of bytes that the directive

@@ Deal with precision range.

-	 can output.  When precision is specified but unknown, use zero
-	 as the minimum since it results in no bytes on output (unless
-	 width is specified to be greater than 0).  */
-      argmin = build_int_cst (argtype, prec && prec != HOST_WIDE_INT_MIN);
+	 can output.  When precision may be zero, use zero as the minimum
+	 since it results in no bytes on output (unless width is specified
+	 to be greater than 0).  */
+      bool zero = dir.prec[0] <= 0 && dir.prec[1] >= 0;
+      argmin = build_int_cst (argtype, !zero);
 
       int typeprec = TYPE_PRECISION (dirtype);
       int argprec = TYPE_PRECISION (argtype);
@@ -1202,8 +1318,8 @@ format_integer (const directive &spec, tree arg)
       /* 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
     {
@@ -1212,3 +1328,3 @@ format_integer (const directive &spec, tree arg)
 	 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).  */

@@ Just a naming change.

-      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;
     }

@@ BOUNDED was removed.

-  /* The result is bounded either when the argument is determined to be
-     (e.g., when it's within some range) or when the minimum and maximum
-     are the same.  That can happen here for example when the specified
-     width is as wide as the greater of MIN and MAX, as would be the case
-     with sprintf (d, "%08x", x) with a 32-bit integer x.  */
-  res.bounded |= res.range.min == res.range.max;
-
   if (res.range.max < res.range.min)
     {
       unsigned HOST_WIDE_INT tmp = res.range.max;
@@ -1230,2 +1339,2 @@ format_integer (const directive &spec, tree arg)
       res.range.min = tmp;
     }

@@ Again, adjust byte counters upward in necessary to account for
@@ precision and range (if specified).

+  res.range.likely = res.knownrange ? res.range.max : res.range.min;
+  res.range.unlikely = res.range.max;
+  res.adjust_for_width_or_precision (dir.width, dirtype, base,
+				     (sign | maybebase) + (base == 16));
+  res.adjust_for_width_or_precision (dir.prec, dirtype, base,
+				     (sign | maybebase) + (base == 16));
+
   return res;
 }
 
@@ -1255,2 +1371,2 @@ get_mpfr_format_length (mpfr_ptr x, const char *flags, HOST_WIDE_INT prec,
   fmtstr[len + 5] = spec;
   fmtstr[len + 6] = '\0';

@@ Some code here may be duplicated from trunk.  As I was testing my changes
@@ I ended up fixing the same problems as later on trunk.  These changes
@@ will be reconciled during a merge (I've already done that in my local
@@ tree).
-  /* Avoid passing negative precisions with larger magnitude to MPFR
-     to avoid exposing its bugs.  (A negative precision is supposed
-     to be ignored.)  */
-  if (prec < 0)
-    prec = -1;
+  spec = TOUPPER (spec);
+  if (spec == 'E' || spec == 'F')
+    {
+      /* For %e, specify the precision explicitly since mpfr_sprintf
+	 does its own thing just to be different (see MPFR bug 21088).  */
+      if (prec < 0)
+	prec = 6;
+    }
+  else
+    {
+      /* Avoid passing negative precisions with larger magnitude to MPFR
+	 to avoid exposing its bugs.  (A negative precision is supposed
+	 to be ignored.)  */
+      if (prec < 0)
+	prec = -1;
+    }
 
   HOST_WIDE_INT p = prec;
 
-  if (TOUPPER (spec) == 'G')
+  if (spec == 'G')
     {
       /* For G/g, precision gives the maximum number of significant
 	 digits which is bounded by LDBL_MAX_10_EXP, or, for a 128
@@ -1311,2 +1438,2 @@ format_floating_max (tree type, char spec, HOST_WIDE_INT prec)
   const real_format *rfmt = REAL_MODE_FORMAT (mode);
   REAL_VALUE_TYPE rv;

@@ Simplify.
-  {
-    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
      round-to-nearest mode.  */
   mpfr_t x;
   mpfr_init2 (x, rfmt->p);

@@ No real change, the constant is the same.

-  mpfr_from_real (x, &rv, GMP_RNDN);
+  mpfr_from_real (x, &rv, MPFR_RNDN);
 
   /* Return a value one greater to account for the leading minus sign.  */
   return 1 + get_mpfr_format_length (x, "", prec, spec, 'D');
@@ -1334,1 +1457,1 @@ format_floating_max (tree type, char spec, HOST_WIDE_INT prec)
    is used when the directive argument or its value isn't known.  */

@@ WIDTH and PRECISION and now part of struct DIRECTIVE.
 static fmtresult
-format_floating (const directive &spec, HOST_WIDE_INT width,
-		 HOST_WIDE_INT prec)
+format_floating (const directive &dir)
 {
   tree type;
   bool ldbl = false;
 
-  switch (spec.modifier)
+  switch (dir.modifier)
     {
     case FMT_LEN_l:
     case FMT_LEN_none:
@@ -1379,8 +1501,8 @@ format_floating (const directive &spec, HOST_WIDE_INT width,
       logexpdigs = ilog (expdigs, 10);
     }
 
-  switch (spec.specifier)
+  switch (dir.specifier)
     {
     case 'A':
     case 'a':
       {

@@ Handle precision range.  (Again, some furher changes here are needed
@@ to merge.  I've already done that in my local tree.)

 	/* The minimum output is "0x.p+0".  */
-	res.range.min = 6 + (prec > 0 ? prec : 0);
-	res.range.max = (width == INT_MIN
-			 ? HOST_WIDE_INT_MAX
-			 : format_floating_max (type, 'a', prec));
-
-	/* The output of "%a" is fully specified only when precision
-	   is explicitly specified and width isn't unknown.  */
-	res.bounded = INT_MIN != width && -1 < prec;
+	res.range.min = 6 + (dir.prec[0] > 0 ? dir.prec[0] : 0);
+	
+	res.range.max = format_floating_max (type, 'a', dir.prec[1]);
+	res.range.likely = res.range.min;
+
+	/* The unlikely maximum accounts for the longest multibyte
+	   decimal point character.  */
+	if (dir.prec[0] != dir.prec[1]
+            || dir.prec[0] == -1 || dir.prec[0] > 0)
+	  res.range.unlikely = res.range.max + target_mb_len_max () - 1;
+	else
+	  res.range.unlikely = res.range.max;
 	break;
       }
 
     case 'E':
     case 'e':
       {

@@ Same as above.

-	bool sign = spec.get_flag ('+') || spec.get_flag (' ');
+	bool sign = dir.get_flag ('+') || dir.get_flag (' ');
 	/* The minimum output is "[-+]1.234567e+00" regardless
 	   of the value of the actual argument.  */
 	res.range.min = (sign
-			 + 1 /* unit */ + (prec < 0 ? 7 : prec ? prec + 1 : 0)
+			 + 1 /* unit */
+			 + (dir.prec[0] < 0
+			    ? 7 : dir.prec[0] ? dir.prec[0] + 1
+			    : 0)
 			 + 2 /* e+ */ + 2);
 	/* Unless width is uknown the maximum output is the minimum plus
 	   sign (unless already included), plus the difference between
 	   the minimum exponent of 2 and the maximum exponent for the type.  */
-	res.range.max = (width == INT_MIN
-			 ? HOST_WIDE_INT_M1U
-			 : res.range.min + !sign + logexpdigs - 2);
-
-	/* "%e" is fully specified and the range of bytes is bounded
-	   unless width is unknown.  */
-	res.bounded = INT_MIN != width;
+	res.range.max = res.range.min + !sign + logexpdigs - 2;
+	res.range.likely = res.range.min;
+
+	/* The unlikely maximum accounts for the longest multibyte
+	   decimal point character.  */
+	if (dir.prec[0] != dir.prec[1]
+            || dir.prec[0] == -1 || dir.prec[0] > 0)
+	  res.range.unlikely = res.range.max + target_mb_len_max () -1;
+	else
+	  res.range.unlikely = res.range.max;
 	break;
       }
 
@@ -1423,0 +1555,0 @@ format_floating (const directive &spec, HOST_WIDE_INT width,

@@ Same as above.

       {
 	/* The minimum output is "1.234567" regardless of the value
 	   of the actual argument.  */
-	res.range.min = 2 + (prec < 0 ? 6 : prec);
+	res.range.min = 2 + (dir.prec[0] < 0 ? 6 : dir.prec[0]);
 
 	/* Compute the maximum just once.  */
 	const HOST_WIDE_INT f_max[] = {
-	  format_floating_max (double_type_node, 'f', prec),
-	  format_floating_max (long_double_type_node, 'f', prec)
+	  format_floating_max (double_type_node, 'f', dir.prec[1]),
+	  format_floating_max (long_double_type_node, 'f', dir.prec[1])
 	};
-	res.range.max = width == INT_MIN ? HOST_WIDE_INT_MAX : f_max [ldbl];
-
-	/* "%f" is fully specified and the range of bytes is bounded
-	   unless width is unknown.  */
-	res.bounded = INT_MIN != width;
+	res.range.max = f_max [ldbl];
+	res.range.likely = res.range.min;
+
+	/* The unlikely maximum accounts for the longest multibyte
+	   decimal point character.  */
+	if (dir.prec[0] != dir.prec[1]
+            || dir.prec[0] == -1 || dir.prec[0] > 0)
+	  res.range.unlikely = res.range.max + target_mb_len_max () - 1;
 	break;
       }
     case 'G':
@@ -1445,1 +1580,1 @@ format_floating (const directive &spec, HOST_WIDE_INT width,
 
@@ Same as above.

 	/* Compute the maximum just once.  */
 	const HOST_WIDE_INT g_max[] = {
-	  format_floating_max (double_type_node, 'g', prec),
-	  format_floating_max (long_double_type_node, 'g', prec)
+	  format_floating_max (double_type_node, 'g', dir.prec[1]),
+	  format_floating_max (long_double_type_node, 'g', dir.prec[1])
 	};
-	res.range.max = width == INT_MIN ? HOST_WIDE_INT_MAX : g_max [ldbl];
+	res.range.max = g_max [ldbl];
+	res.range.likely = g_max [ldbl];
 
-	/* "%g" is fully specified and the range of bytes is bounded
-	   unless width is unknown.  */
-	res.bounded = INT_MIN != width;
+	/* The unlikely maximum accounts for the longest multibyte
+	   decimal point character.  */
+	res.range.unlikely = res.range.max + target_mb_len_max () - 1;
 	break;
       }
 
@@ -1460,2 +1596,2 @@ format_floating (const directive &spec, HOST_WIDE_INT width,
       return fmtresult ();
     }

@@ Simplify.

-  if (width > 0)
-    {
-      if (res.range.min < (unsigned)width)
-	res.range.min = width;
-      if (res.range.max < (unsigned)width)
-	res.range.max = width;
-    }
-
+  /* Bump up the byte counters if WIDTH is greater.  */
+  res.adjust_for_width_or_precision (dir.width);
   return res;
 }
 
@@ -1476,1 +1606,1 @@ format_floating (const directive &spec, HOST_WIDE_INT width,
    floating argument ARG.  */

@@ Get rid of the local WIDTH and PRECISION since they're both in struct
@@ DIRECTIVE as ranges.

 static fmtresult
-format_floating (const directive &spec, tree arg)
+format_floating (const directive &dir, tree arg)
 {
-  /* Set WIDTH to -1 when it's not specified, to INT_MIN when it is
-     specified by the asterisk to an unknown value, and otherwise to
-     a non-negative value corresponding to the specified width.  */
-  HOST_WIDE_INT width = -1;
-  HOST_WIDE_INT prec = -1;

@@ Detect non-constant arguments first and call the non-constant overload
@@ to avoid conditionals and nesting below.

+  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[0], dir.prec[1] };
 
-  if (spec.have_width)
-    width = spec.width;
-  else if (spec.star_width)
+  if (TOUPPER (dir.specifier) == 'A')
     {

@@ Handle precision range for floating point constants.  Deal with
@@ the two known %a formats (with and without redundant trailing
@@ zeros) due to poor specification.

-      if (TREE_CODE (spec.star_width) == INTEGER_CST)
+      /* For %a, leave the minimum precision unspecified to let
+	 MFPR trim trailing zeros (as it and many other systems
+	 including Glibc happen to do) and set the maximum
+	 precision to reflect what it would be with trailing zeros
+	 present (as Solaris and derived systems do).  */
+      if (prec[0] < 0)
+	prec[0] = -1;
+      if (prec[1] < 0)
 	{
-	  width = tree_to_shwi (spec.star_width);
-	  if (width < 0)
-	    width = -width;
+	  unsigned fmtprec
+	    = (dir.modifier == FMT_LEN_L
+	       ? REAL_MODE_FORMAT (XFmode)->p
+	       : REAL_MODE_FORMAT (DFmode)->p);
+
+	  /* The precision of the IEEE 754 double format is 53.
+	     The precision of all other GCC binary double formats
+	     is 56 or less.  */
+	  prec[1] = fmtprec <= 56 ? 13 : 15;
 	}
-      else
-	width = INT_MIN;
     }

@@ Continue handling precision ranges (removing the FIXME...)

-  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);
-      else
-	{
-	  /* FIXME: Handle non-constant precision.  */
-	  res.range.min = res.range.max = HOST_WIDE_INT_M1U;
-	  return res;
-	}
-    }
-  else if (res.constant && TOUPPER (spec.specifier) != 'A')
-    {
-      /* Specify the precision explicitly since mpfr_sprintf defaults
-	 to zero.  */
-      prec = 6;
-    }
+  /* The minimum and maximum number of bytes produced by the directive.  */
+  fmtresult res;
 
-  if (res.constant)
-    {
-      /* Set up an array to easily iterate over.  */
-      unsigned HOST_WIDE_INT* const minmax[] = {
-	&res.range.min, &res.range.max
-      };

@@ Continuing to adjust code to handle precision ranges.  The overall
@@ logic stays the same, just a bit more complex.

+  /* Set up an array to easily iterate over.  */
+  unsigned HOST_WIDE_INT* const minmax[] = {
+    &res.range.min, &res.range.max
+  };
+
+  /* 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)));
 
-      /* 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)));
+  char fmtstr [40];
+  char *pfmt = fmtstr;
 
+  /* Append flags.  */
+  for (const char *pf = "-+ #0"; *pf; ++pf)
+    if (dir.get_flag (*pf))
+      *pfmt++ = *pf;
+
+  *pfmt = '\0';
+
+  for (int i = 0; i != sizeof minmax / sizeof *minmax; ++i)
+    {
       /* 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.  */
+	 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, GMP_RNDN);
-
-      char fmtstr [40];
-      char *pfmt = fmtstr;
+      mpfr_from_real (mpfrval, rvp, i ? MPFR_RNDU : MPFR_RNDD);
 
-      /* Append flags.  */
-      for (const char *pf = "-+ #0"; *pf; ++pf)
-	if (spec.get_flag (*pf))
-	  *pfmt++ = *pf;
+      /* 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];
 
-      *pfmt = '\0';
+      /* Format it and store the result in the corresponding member
+	 of the result struct.  */
+      *minmax[i] = get_mpfr_format_length (mpfrval, fmtstr, prec[i],
+					   dir.specifier, rndspec);
+    }
 
-      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;
-	}
+  if (res.range.max < res.range.min)
+    {
+      /* Swap the two bounds if rounding resulted in the maximum being
+	 less than the minimum.  */
+      unsigned HOST_WIDE_INT tmp = res.range.max;
+      res.range.max = res.range.min;
+      res.range.min = tmp;
+    }
 
-      /* The range of output is known even if the result isn't bounded.  */
-      if (width == INT_MIN)
-	{
-	  res.knownrange = false;
-	  res.range.max = HOST_WIDE_INT_MAX;
-	}
-      else
-	res.knownrange = true;
+  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 ());
+  /* For the same floating point constant use the longer output
+     as the likely maximum since with round to nearest either is
+     equally likely. */
+  res.range.likely = res.range.max;
+  res.range.unlikely = res.range.max;
 
-      return res;

@@ When precision is not zero the decimal point may be present.
@@ Make the UNLIKELY counter reflect the longest decimal point
@@ (i.e., MB_LEN_MAX bytes).

+  if (prec[0] != 0 || prec[1] != 0)
+    {
+      /* Unless the precision is zero the output may include
+	 the decimal point which must be a single character up
+	 to MB_LEN_MAX in length.  This is overly conservative
+	 since in some conversions some constants result in no
+	 decimal point (e.g., in %g).  */
+      res.range.unlikely += target_mb_len_max () - 1;
     }

@@ Non-constant arguments were detected at the top of the function.

-  return format_floating (spec, width, prec);

@@ And adjust for width (precision was handled above).

+  res.adjust_for_width_or_precision (dir.width);
+  return res;
 }
 
 /* Return a FMTRESULT struct set to the lengths of the shortest and longest
@@ -1605,0 +1724,0 @@ get_string_length (tree str)

@@ Simplify by making use of new FMTRESULT ctor.

   if (tree slen = c_strlen (str, 1))
     {
       /* Simply return the length of the string.  */
-      fmtresult res;
-      res.range.min = res.range.max = tree_to_shwi (slen);
-      res.bounded = true;
-      res.constant = true;
-      res.knownrange = true;
+      fmtresult res (tree_to_shwi (slen));
       return res;
     }
 
@@ -1623,20 +1738,11 @@ get_string_length (tree str)
 
   if (lenrange [0] || lenrange [1])
     {
-      fmtresult res;
-
-      res.range.min = (tree_fits_uhwi_p (lenrange[0])
-		       ? tree_to_uhwi (lenrange[0]) : 1 < warn_format_length);
-      res.range.max = (tree_fits_uhwi_p (lenrange[1])
-		       ? tree_to_uhwi (lenrange[1]) : HOST_WIDE_INT_M1U);
+      fmtresult res (tree_fits_uhwi_p (lenrange[0])
+		     ? tree_to_uhwi (lenrange[0]) : 1 < warn_format_length,
+		     tree_fits_uhwi_p (lenrange[1])
+		     ? tree_to_uhwi (lenrange[1]) : HOST_WIDE_INT_M1U);
 
-      /* Set RES.BOUNDED to true if and only if all strings referenced
+      /* Set RES.KNOWNRANGE to true if and only if all strings referenced
 	 by STR are known to be bounded (though not necessarily by their
 	 actual length but perhaps by their maximum possible length).  */
-      res.bounded = res.range.max < target_int_max ();
-      res.knownrange = res.bounded;
-
-      /* Set RES.CONSTANT to false even though that may be overly
-	 conservative in rare cases like: 'x ? a : b' where a and
-	 b have the same lengths and consist of the same characters.  */
-      res.constant = false;

@@ The maximum is SIZE_MAX when the length of the longest string is unknown
@@ (unbounded).

+      if (res.range.max < target_int_max ())
+	{
+	  res.knownrange = true;
+	  res.range.likely = res.range.max;
+	}
+      else
+	res.range.likely = res.range.min;
 
+      res.range.unlikely = res.range.max;
       return res;
     }
 
@@ -1648,6 +1762,6 @@ get_string_length (tree str)
 }
 
 /* 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).  */

@@ Introduce format_character to simply the format_string function
@@ which previously handled both and was getting hard to follow
@@ with the range changes.

 static fmtresult
-format_string (const directive &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 : 1 < warn_format_length ? 6 : 1);
-
-  /* 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 : 1 < warn_format_length);
-
-  /* The result is bounded unless overriddden for a non-constant string
-     of an unknown length.  */
-  bool bounded = true;
+  res.knownrange = true;
 
-  if (spec.specifier == 'c')
+  if (dir.modifier == FMT_LEN_l)
     {
-      if (spec.modifier == FMT_LEN_l)

@@ Use get_int_range to constrain the expected length of a constant
@@ wide character in the ASCII range or for NUL.  This helps avoid
@@ warnings for clearly safe code.  Set the LIKELY counter to 2 but
@@ UNLIKELY to MB_LEN_MAX.  The diff is hard to read because it's
@@ mixed with the changes to format_string below.

+      res.range.min = 0;
+      HOST_WIDE_INT min, max;
+      if (get_int_range (arg, integer_type_node, &min, &max, false, 0))
 	{
-	  /* 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 = 1 == warn_format_length ? !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;
+	  if (min == 0 && max == 0)
+	    {
+	      res.range.min = res.range.max = 0;
+	      res.range.likely = 0;
+	    }
+	  else if (0 < min && min < 128)
+	    {
+	      res.range.max = target_mb_len_max ();
+	      res.range.likely = 1;
+	    }
+	  else
+	    {
+	      res.range.max = target_mb_len_max ();
+	      res.range.likely = 2;
+	    }
 	}
       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;
+	  res.range.max = target_mb_len_max ();
+	  res.range.likely = 2;
 	}
+
+      res.range.unlikely = res.range.max;
     }
-  else   /* spec.specifier == 's' */
+  else
     {
-      /* 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 plain '%c' directive.  Its ouput is exactly 1.  */
+      res.range.min = res.range.max = 1;
+      res.range.likely = res.range.unlikely = 1;
+      res.knownrange = true;
+    }
 
-	  /* A '%s' directive with a string argument with constant length.  */
-	  res.range = slen.range;
+  /* Bump up the byte counters if WIDTH is greater.  */
+  return res.adjust_for_width_or_precision (dir.width);
+}
 
-	  /* 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;
+/* Return the minimum and maximum number of characters formatted
+   by the '%s' format directive and its wide character form for
+   the argument ARG.  ARG can be null (for functions such as
+   vsprinf).  */
 
-	  if (spec.modifier == FMT_LEN_l)
-	    {
-	      bounded = false;

@@ Format_string becomes simpler thanks to format_character.  It also
@@ gets more involved because it needs to handle width and precision
@@ ranges.

+static fmtresult
+format_string (const directive &dir, tree arg)
+{
+  fmtresult res;
 
-	      if (warn_format_length > 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;
-		}

@@ Nothing new, the code is just less indented  (some of existing code
@@ is farther up the diff.

+  /* Compute the range the argument's length can be in.  */
+  fmtresult slen = get_string_length (arg);
+  if (slen.range.min == slen.range.max
+      && slen.range.min < HOST_WIDE_INT_MAX)
+    {
+      /* The argument is either a string constant or it refers
+	 to one of a number of strings of the same length.  */
 
-	      /* 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)
-	    {
-	      /* 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;
-	    }
+      /* A '%s' directive with a string argument with constant length.  */
+      res.range = slen.range;
 
-	  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)
+      if (dir.modifier == FMT_LEN_l)
+	{
+	  /* In the worst case the length of output of a wide string S
+	     is bounded by MB_LEN_MAX * wcslen (S). */
+	  res.range.max *= target_mb_len_max ();
+	  res.range.unlikely = res.range.max;
+	  /* It's likely that the the total length is not more that
+	     2 * wcslen (S).*/
+	  res.range.likely = res.range.min * 2;
+
+	  if (0 <= dir.prec[1]
+	      && (unsigned HOST_WIDE_INT)dir.prec[1] < res.range.max)
 	    {
-	      /* When precision is specified but not known the lower
-		 bound is assumed to be as low as zero.  */
-	      res.range.min = 0;
+	      res.range.max = dir.prec[1];
+	      res.range.likely = dir.prec[1];
+	      res.range.unlikely = dir.prec[1];
 	    }
-	}
-      else if (arg && integer_zerop (arg))
-	{
-	  /* Handle null pointer argument.  */
 
-	  fmtresult res;
+	  if (dir.prec[0] < 0 && dir.prec[1] > -1)
+	    res.range.min = 0;
+	  else if (0 <= dir.prec[0])
+	    res.range.likely = dir.prec[0];
+
+	  /* Even a non-empty wide character string need not convert into
+	     any bytes.  */
 	  res.range.min = 0;
-	  res.range.max = HOST_WIDE_INT_MAX;
-	  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.  */
+	  res.knownrange = true;
+
+	  if (dir.prec[0] < 0 && dir.prec[1] > -1)
+	    res.range.min = 0;
+	  else if ((unsigned HOST_WIDE_INT)dir.prec[0] < res.range.min)
+	    res.range.min = dir.prec[0];
+
+	  if ((unsigned HOST_WIDE_INT)dir.prec[1] < res.range.max)
+	    {
+	      res.range.max = dir.prec[1];
+	      res.range.likely = dir.prec[1];
+	      res.range.unlikely = dir.prec[1];
+	    }
+	}
+    }
+  else if (arg && integer_zerop (arg))
+    {
+      /* Handle null pointer argument.  */
+
+      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.  */
 
-	  if (0 <= prec)
+      if (0 <= dir.prec[0])
+	{
+	  if (slen.range.min >= target_int_max ())
+	    slen.range.min = 0;
+	  else if ((unsigned HOST_WIDE_INT)dir.prec[0] < slen.range.min)
 	    {
-	      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;
+	      slen.range.min = dir.prec[0];
+	      slen.range.likely = slen.range.min;
 	    }
-	  else if (slen.range.min >= target_int_max ())
+
+	  if ((unsigned HOST_WIDE_INT)dir.prec[1] < slen.range.max
+	      || slen.range.max >= target_int_max ())
 	    {
-	      slen.range.min = max_bytes_for_unknown_str;
-	      slen.range.max = max_bytes_for_unknown_str;
-	      bounded = false;
+	      slen.range.max = dir.prec[1];
+	      slen.range.likely = slen.range.max;
 	    }
+	}
+      else if (slen.range.min >= target_int_max ())
+	{
+	  slen.range.min = 0;
+	  slen.range.max = HOST_WIDE_INT_MAX;
+	  slen.range.likely = 1;
+	}
 
-	  res.range = slen.range;
+      slen.range.unlikely = slen.range.max;
 
-	  /* 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;
-	}
+      res.range = slen.range;
+      res.knownrange = slen.knownrange;
     }
 
-  /* Adjust the lengths for field width.  */
-  if (0 < width)
-    {
-      if (res.range.min < (unsigned HOST_WIDE_INT)width)
-	res.range.min = width;

@@ Same as in most other handlers, adjust upward for width (precision
@@ is handled above because it adjusts downward).

+  /* Bump up the byte counters if WIDTH is greater.  */
+  return res.adjust_for_width_or_precision (dir.width);
+}
 
-      if (res.range.max < (unsigned HOST_WIDE_INT)width)
-	res.range.max = width;
+/* Format plain string (part of the format string itself).  */
 
-      /* Adjust BOUNDED if width happens to make them equal.  */
-      if (res.range.min == res.range.max && res.range.min < target_int_max ()
-	  && bounded)
-	res.bounded = true;

@@ A trivial handler to handle plain characters in a format string.
@@ They are also called directives by C and POSIX and handling them
@@ the same as directives that start with a '%' simplifies the
@@ format_directive function quite a bit.

+static fmtresult
+format_plain (const directive &dir, tree)
+{
+  fmtresult res (dir.len);
+  return res;
+}
+

@@ A simple function that determines whether or not an individual
@@ directive should be warned about.  The function needs a comment.
@@ I added it during the merge.  (The function also becomes more
@@ involved with the merge due to the introduction of
@@ -Wformat-truncation.)

+static bool
+should_warn_p (const result_range &avail, const result_range &result)
+{
+  if (result.max <= avail.min)
+    {
+      /* The least amount of space remaining in the destination is big
+	 enough for the longest output.  */
+      return false;
     }
 
-  /* When precision is specified the range of characters on output
-     is known to be bounded by it.  */
-  if (-1 < width && -1 < prec)
-    res.knownrange = true;
+  if (1 == warn_format_length
+      && result.likely <= avail.likely)
+    {
+      /* The likely amount of space remaining in the destination is big
+	 enough for the likely output.  */
+      return false;
+    }
 
-  return res;
+  if (warn_format_length == 2
+      && result.likely <= avail.min
+      && (result.max <= avail.min
+	  || result.max > HOST_WIDE_INT_MAX))
+    {
+      /* The minimum amount of space remaining in the destination is big
+	 enough for the longest output.  */
+      return false;
+    }
+
+  return true;
 }

@@ Introduce the maybe_warn function to simplify the format_directive
@@ function.  This keeps all the warnings about overflow/truncation
@@ in a directive in the same place.  The change also let me notice
@@ some inconsistencies in the diagnostics in a corner case or two.
@@ There are quite a few conditionals below but the differences
@@ between them are often only subtle (e.g., "writing" or "truncated
@@ vs "may write" or "may be truncated", plural or singular, and
@@ constants vs ranges).

+/* At format string location describe by DIRLOC in a call described
+   by INFO, issue a warning for a directive DIR whose output may be
+   in excess of the available space AVAIL_RANGE in the destination
+   given the formatting result FMTRES.  This function does nothing
+   except decide whether to issue a warning for a possible write
+   past the end or truncation and, if so, format the warning.
+   Return true if a warning has been issued.  */
 
-static fmtresult
-format_plain (const directive &spec, tree)
+static bool
+maybe_warn (substring_loc &dirloc, source_range *pargrange,
+	    const pass_sprintf_length::call_info &info,
+	    const result_range &avail_range, const result_range &res,
+	    const directive &dir)
 {
-  fmtresult res;
-  res.argmin = res.argmax = NULL_TREE;
-  res.range.min = res.range.max = spec.len;
-  res.bounded = res.knownrange = res.constant = true;
-  return res;
+  if (!should_warn_p (avail_range, res))
+    return false;
+
+  /* A warning will definitely be issued below.  */
+
+  /* The maximum byte count to reference in the warning.  Larger counts
+     imply that the upper bound is unknown (and could be anywhere between
+     RES.MIN + 1 and SIZE_MAX / 2) are printed as "N or more bytes" rather
+     than "between N and X" where X is some huge number.  */
+  unsigned HOST_WIDE_INT maxbytes = target_dir_max ();
+
+  /* True when there is enough room in the destination for the least
+     amount of a directive's output but not enough for its likely or
+     maximum output.  */
+  bool maybe = (res.min <= avail_range.max
+		&& (avail_range.min < res.likely
+		    || (res.max < HOST_WIDE_INT_MAX
+			&& avail_range.min < res.max)));
+
+  if (avail_range.min == avail_range.max)
+    {
+      /* The size of the destination region is exact.  */
+      unsigned HOST_WIDE_INT navail = avail_range.max;
+
+      if (*dir.beg != '%')
+	{
+	  /* For plain character directives (i.e., the format string itself)
+	     but not others, point the caret at the first character that's
+	     past the end of the destination.  */
+	  dirloc.set_caret_index (dirloc.get_caret_idx () + navail);
+	}
+
+      if (*dir.beg == '\0')
+	{
+	  /* This is the terminating nul.  */
+	  gcc_assert (res.min == 1 && res.min == res.max);
+
+	  const char *fmtstr
+	    = (info.bounded
+	       ? (maybe
+		  ? G_("%qE output may be truncated before the last format "
+		       "character")
+		  : G_("%qE output truncated before the last format character"))
+	       : (maybe
+		  ? G_("%qE may write a terminating nul past the end "
+		       "of the destination")
+		  : G_("%qE writing a terminating nul past the end "
+		       "of the destination")));
+
+	  return fmtwarn (dirloc, NULL, NULL, OPT_Wformat_length_, fmtstr,
+			  info.func);
+	}
+
+      if (res.min == res.max)
+	{
+	  const char* fmtstr
+	    = (res.min == 1
+	       ? (info.bounded
+		  ? (maybe
+		     ? G_("%<%.*s%> directive output may be truncated writing "
+			  "%wu byte into a region of size %wu")
+		     : G_("%<%.*s%> directive output truncated writing "
+			  "%wu byte into a region of size %wu"))
+		  : G_("%<%.*s%> directive writing %wu byte "
+		       "into a region of size %wu"))
+	       : (info.bounded
+		  ? (maybe
+		     ? G_("%<%.*s%> directive output may be truncated writing "
+			  "%wu bytes into a region of size %wu")
+		     : G_("%<%.*s%> directive output truncated writing "
+			  "%wu bytes into a region of size %wu"))
+		  : G_("%<%.*s%> directive writing %wu bytes "
+		       "into a region of size %wu")));
+	  return fmtwarn (dirloc, pargrange, NULL,
+			  OPT_Wformat_length_, fmtstr,
+			  dir.len, dir.beg, res.min,
+			  navail);
+	}
+
+      if (0 == res.min && res.max < maxbytes)
+	{
+	  const char* fmtstr
+	    = (info.bounded
+	       ? (maybe
+		  ? G_("%<%.*s%> directive output may be truncated writing "
+		       "up to %wu bytes into a region of size %wu")
+		  : G_("%<%.*s%> directive output truncated writing "
+		       "up to %wu bytes into a region of size %wu"))
+	       : G_("%<%.*s%> directive writing up to %wu bytes "
+		    "into a region of size %wu"));
+	  return fmtwarn (dirloc, pargrange, NULL,
+			  OPT_Wformat_length_, fmtstr,
+			  dir.len, dir.beg,
+			  res.max, navail);
+	}
+
+      if (0 == res.min && maxbytes <= res.max)
+	{
+	  /* This is a special case to avoid issuing the potentially
+	     confusing warning:
+  	       writing 0 or more bytes into a region of size 0.  */
+	  const char* fmtstr
+	    = (info.bounded
+	       ? (maybe
+		  ? G_("%<%.*s%> directive output may be truncated writing "
+		       "likely %wu or more bytes into a region of size %wu")
+		  : G_("%<%.*s%> directive output truncated writing "
+		       "likely %wu or more bytes into a region of size %wu"))
+	       : G_("%<%.*s%> directive writing likely %wu or more bytes "
+		    "into a region of size %wu"));
+	  return fmtwarn (dirloc, pargrange, NULL,
+			  OPT_Wformat_length_, fmtstr,
+			  dir.len, dir.beg,
+			  res.likely, navail);
+	}
+
+      if (res.max < maxbytes)
+	{
+	  const char* fmtstr
+	    = (info.bounded
+	       ? (maybe
+		  ? G_("%<%.*s%> directive output may be truncated writing "
+		       "between %wu and %wu bytes into a region of size %wu")
+		  : G_("%<%.*s%> directive output truncated writing "
+		       "between %wu and %wu bytes into a region of size %wu"))
+	       : G_("%<%.*s%> directive writing between %wu and "
+		    "%wu bytes into a region of size %wu"));
+	  return fmtwarn (dirloc, pargrange, NULL,
+			  OPT_Wformat_length_, fmtstr,
+			  dir.len, dir.beg,
+			  res.min, res.max,
+			  navail);
+	}
+
+      const char* fmtstr
+	= (info.bounded
+	   ? (maybe
+	      ? G_("%<%.*s%> directive output may be truncated writing "
+		   "%wu or more bytes into a region of size %wu")
+	      : G_("%<%.*s%> directive output truncated writing "
+		   "%wu or more bytes into a region of size %wu"))
+	   : G_("%<%.*s%> directive writing %wu or more bytes "
+		"into a region of size %wu"));
+      return fmtwarn (dirloc, pargrange, NULL,
+		      OPT_Wformat_length_, fmtstr,
+		      dir.len, dir.beg,
+		      res.min, navail);
+    }
+
+  /* The size of the destination region is a range.  */
+
+  if (*dir.beg != '%')
+    {
+      unsigned HOST_WIDE_INT navail = avail_range.max;
+
+      /* For plain character directives (i.e., the format string itself)
+	 but not others, point the caret at the first character that's
+	 past the end of the destination.  */
+      dirloc.set_caret_index (dirloc.get_caret_idx () + navail);
+    }
+
+  if (*dir.beg == '\0')
+    {
+      gcc_assert (res.min == 1 && res.min == res.max);
+
+      const char *fmtstr
+	= (info.bounded
+	   ? (maybe
+	      ? G_("%qE output may be truncated before the last format "
+		   "character")
+	      : G_("%qE output truncated before the last format character"))
+	   : (maybe
+	      ? G_("%qE may write a terminating nul past the end "
+		   "of the destination")
+	      : G_("%qE writing a terminating nul past the end "
+		   "of the destination")));
+
+      return fmtwarn (dirloc, NULL, NULL, OPT_Wformat_length_, fmtstr,
+		      info.func);
+    }
+
+  if (res.min == res.max)
+    {
+      const char* fmtstr
+	= (res.min == 1
+	   ? (info.bounded
+	      ? (maybe
+		 ? G_("%<%.*s%> directive output may be truncated writing "
+		      "%wu byte into a region of size between %wu and %wu")
+		 : G_("%<%.*s%> directive output truncated writing "
+		      "%wu byte into a region of size between %wu and %wu"))
+	      : G_("%<%.*s%> directive writing %wu byte "
+		   "into a region of size between %wu and %wu"))
+	   : (info.bounded
+	      ? (maybe
+		 ? G_("%<%.*s%> directive output may be truncated writing "
+		      "%wu bytes into a region of size between %wu and %wu")
+		 : G_("%<%.*s%> directive output truncated writing "
+		      "%wu bytes into a region of size between %wu and %wu"))
+	      : G_("%<%.*s%> directive writing %wu bytes "
+		   "into a region of size between %wu and %wu")));
+
+      return fmtwarn (dirloc, pargrange, NULL,
+		      OPT_Wformat_length_, fmtstr,
+		      dir.len, dir.beg, res.min,
+		      avail_range.min, avail_range.max);
+    }
+
+  if (0 == res.min && res.max < maxbytes)
+    {
+      const char* fmtstr
+	= (info.bounded
+	   ? (maybe
+	      ? G_("%<%.*s%> directive output may be truncated writing "
+		   "up to %wu bytes into a region of size between "
+		   "%wu and %wu")
+	      : G_("%<%.*s%> directive output truncated writing "
+		   "up to %wu bytes into a region of size between "
+		   "%wu and %wu"))
+	   : G_("%<%.*s%> directive writing up to %wu bytes "
+		"into a region of size between %wu and %wu"));
+      return fmtwarn (dirloc, pargrange, NULL,
+		      OPT_Wformat_length_, fmtstr,
+		      dir.len, dir.beg, res.max,
+		      avail_range.min, avail_range.max);
+    }
+
+  if (0 == res.min && maxbytes <= res.max)
+    {
+      /* This is a special case to avoid issuing the potentially confusing
+	 warning:
+	   writing 0 or more bytes into a region of size between 0 and N.  */
+      const char* fmtstr
+	= (info.bounded
+	   ? (maybe
+	      ? G_("%<%.*s%> directive output may be truncated writing "
+		   "likely %wu or more bytes into a region of size between "
+		   "%wu and %wu")
+	      : G_("%<%.*s%> directive output truncated writing likely "
+		   "%wu or more bytes into a region of size between "
+		   "%wu and %wu"))
+	   : G_("%<%.*s%> directive writing likely %wu or more bytes "
+		"into a region of size between %wu and %wu"));
+      return fmtwarn (dirloc, pargrange, NULL,
+		      OPT_Wformat_length_, fmtstr,
+		      dir.len, dir.beg, res.likely,
+		      avail_range.min, avail_range.max);
+    }
+
+  if (res.max < maxbytes)
+    {
+      const char* fmtstr
+	= (info.bounded
+	   ? (maybe
+	      ? G_("%<%.*s%> directive output may be truncated writing "
+		   "between %wu and %wu bytes into a region of size "
+		   "between %wu and %wu")
+	      : G_("%<%.*s%> directive output truncated writing "
+		   "between %wu and %wu bytes into a region of size "
+		   "between %wu and %wu"))
+	   : G_("%<%.*s%> directive writing between %wu and "
+		"%wu bytes into a region of size between %wu and %wu"));
+      return fmtwarn (dirloc, pargrange, NULL,
+		      OPT_Wformat_length_, fmtstr,
+		      dir.len, dir.beg,
+		      res.min, res.max,
+		      avail_range.min, avail_range.max);
+    }
+
+  const char* fmtstr
+    = (info.bounded
+       ? (maybe
+	  ? G_("%<%.*s%> directive output may be truncated writing "
+	       "%wu or more bytes into a region of size between "
+	       "%wu and %wu")
+	  : G_("%<%.*s%> directive output truncated writing "
+	       "%wu or more bytes into a region of size between "
+	       "%wu and %wu"))
+       : G_("%<%.*s%> directive writing %wu or more bytes "
+	    "into a region of size between %wu and %wu"));
+  return fmtwarn (dirloc, pargrange, NULL,
+		  OPT_Wformat_length_, fmtstr,
+		  dir.len, dir.beg,
+		  res.min,
+		  avail_range.min, avail_range.max);
 }
 
 /* Compute the length of the output resulting from the conversion
@@ -1871,0 +2283,0 @@ format_directive (const pass_sprintf_length::call_info &info,

@@ Format_directive becomes simpler thanks to moving many warnings
@@ into maybe_warn.  Some of the changes below are just due to the
@@ renaming of the byte counters.

   /* Offset of the beginning of the directive from the beginning
      of the format string.  */
   size_t offset = dir.beg - info.fmtstr;
+  size_t start = offset;
+  size_t length = offset + dir.len - !!dir.len;
 
   /* Create a location for the whole directive from the % to the format
      specifier.  */
   substring_loc dirloc (info.fmtloc, TREE_TYPE (info.format),
-			offset, offset, offset + dir.len - 1);
+			offset, start, length);
 
   /* Also create a location range for the argument if possible.
      This doesn't work for integer literals or function calls.  */
@@ -1891,17 +2305,12 @@ format_directive (const pass_sprintf_length::call_info &info,
 
   /* Bail when there is no function to compute the output length,
      or when minimum length checking has been disabled.   */
-  if (!dir.fmtfunc || res->number_chars_min >= HOST_WIDE_INT_MAX)
+  if (!dir.fmtfunc || res->range.min >= HOST_WIDE_INT_MAX)
     return false;
 
-  /* Compute the (approximate) length of the formatted output.  */
+  /* Compute the range of lengths of the formatted output.  */
   fmtresult fmtres = dir.fmtfunc (dir, dir.arg);
 
-  /* The overall result is bounded and constant only if the output
-     of every directive is bounded and constant, respectively.  */
-  res->bounded &= fmtres.bounded;
-  res->constant &= fmtres.constant;
-
   /* Record whether the output of all directives is known to be
      bounded by some maximum, implying that their arguments are
      either known exactly or determined to be in a known range
@@ -1909,3 +2318,3 @@ format_directive (const pass_sprintf_length::call_info &info,
      they refer to.  */
   res->knownrange &= fmtres.knownrange;
 
@@ I have removed this during the merge.

+#if 1
   if (!fmtres.knownrange)
     {
       /* Only when the range is known, check it against the host value
@@ -1928,8 +2338,7 @@ format_directive (const pass_sprintf_length::call_info &info,
 	     to determine the maximum number of characters (for example
 	     for wide characters or wide character strings) but continue
 	     tracking the minimum number of characters.  */
-	  res->number_chars_max = HOST_WIDE_INT_M1U;
-	  res->number_chars = HOST_WIDE_INT_M1U;
+	  res->range.max = HOST_WIDE_INT_M1U;
 	}
 
       if (fmtres.range.min > target_dir_max ())
@@ -1938,10 +2347,10 @@ format_directive (const pass_sprintf_length::call_info &info,
 	     even the minimum number of characters (it shouldn't happen
 	     except in an error) but keep tracking the minimum and maximum
 	     number of characters.  */
-	  res->number_chars = HOST_WIDE_INT_M1U;
 	  return true;
 	}
     }
+#endif
 
   const char *dirbeg = dir.beg;
   int dirlen = dir.len;
@@ -1955,14 +2364,11 @@ format_directive (const pass_sprintf_length::call_info &info,
 
       /* Don't bother processing the rest of the format string.  */
       res->warned = true;
-      res->number_chars = HOST_WIDE_INT_M1U;
-      res->number_chars_min = res->number_chars_max = res->number_chars;
+      res->range.min = HOST_WIDE_INT_M1U;
+      res->range.max = HOST_WIDE_INT_M1U;
       return false;
     }
 
-  bool warned = res->warned;
-
   /* Compute the number of available bytes in the destination.  There
      must always be at least one byte of space for the terminating
      NUL that's appended after the format string has been processed.  */
-  unsigned HOST_WIDE_INT navail = min_bytes_remaining (info.objsize, *res);

@@ I have removed this comment during the merge.

+  // unsigned HOST_WIDE_INT navail = min_bytes_remaining (info.objsize, *res);
+  result_range avail_range = bytes_remaining (info.objsize, *res);
 
-  if (fmtres.range.min < fmtres.range.max)
-    {
-      /* The result is a range (i.e., it's inexact).  */
-      if (!warned)
-	{
-	  if (navail < fmtres.range.min)
-	    {
-	      /* The minimum directive output is longer than there is
-		 room in the destination.  */
-	      if (fmtres.range.min == fmtres.range.max)
-		{
-		  const char* fmtstr
-		    = (info.bounded
-		       ? G_("%<%.*s%> directive output truncated writing "
-			    "%wu bytes into a region of size %wu")
-		       : G_("%<%.*s%> directive writing %wu bytes "
-			    "into a region of size %wu"));
-		  warned = fmtwarn (dirloc, pargrange, NULL,
-				    OPT_Wformat_length_, fmtstr,
-				    dirlen, dirbeg, fmtres.range.min,
-				    navail);
-		}
-	      else if (fmtres.range.max < HOST_WIDE_INT_MAX)
-		{
-		  const char* fmtstr
-		    = (info.bounded
-		       ? G_("%<%.*s%> directive output truncated writing "
-			    "between %wu and %wu bytes into a region of "
-			    "size %wu")
-		       : G_("%<%.*s%> directive writing between %wu and "
-			    "%wu bytes into a region of size %wu"));
-		  warned = fmtwarn (dirloc, pargrange, NULL,
-				    OPT_Wformat_length_, fmtstr,
-				    dirlen, dirbeg,
-				    fmtres.range.min, fmtres.range.max, navail);
-		}
-	      else
-		{
-		  const char* fmtstr
-		    = (info.bounded
-		       ? G_("%<%.*s%> directive output truncated writing "
-			    "%wu or more bytes into a region of size %wu")
-		       : G_("%<%.*s%> directive writing %wu or more bytes "
-			    "into a region of size %wu"));
-		  warned = fmtwarn (dirloc, pargrange, NULL,
-				    OPT_Wformat_length_, fmtstr,
-				    dirlen, dirbeg,
-				    fmtres.range.min, navail);
-		}
-	    }
-	  else if (navail < fmtres.range.max
-		   && (((dir.specifier == 's'
-			 && fmtres.range.max < HOST_WIDE_INT_MAX)
-			/* && (dir.precision || dir.star_precision) */)
-		       || 1 < warn_format_length))
-	    {
-	      /* The maximum directive output is longer than there is
-		 room in the destination and the output length is either
-		 explicitly constrained by the precision (for strings)
- 		 or the warning level is greater than 1.  */
-	      if (fmtres.range.max >= HOST_WIDE_INT_MAX)
-		{
-		  const char* fmtstr
-		    = (info.bounded
-		       ? G_("%<%.*s%> directive output may be truncated "
-			    "writing %wu or more bytes a region of size %wu")
-		       : G_("%<%.*s%> directive writing %wu or more bytes "
-			    "into a region of size %wu"));
-		  warned = fmtwarn (dirloc, pargrange, NULL,
-				    OPT_Wformat_length_, fmtstr,
-				    dirlen, dirbeg,
-				    fmtres.range.min, navail);
-		}
-	      else
-		{
-		  const char* fmtstr
-		    = (info.bounded
-		       ? G_("%<%.*s%> directive output may be truncated "
-			    "writing between %wu and %wu bytes into a region "
-			    "of size %wu")
-		       : G_("%<%.*s%> directive writing between %wu and %wu "
-			    "bytes into a region of size %wu"));
-		  warned = fmtwarn (dirloc, pargrange, NULL,
-				    OPT_Wformat_length_, fmtstr,
-				    dirlen, dirbeg,
-				    fmtres.range.min, fmtres.range.max,
-				    navail);
-		}
-	    }
-	}

@@ No iportant changes here...

+  bool warned = res->warned;
 
-      /* Disable exact length checking but adjust the minimum and maximum.  */
-      res->number_chars = HOST_WIDE_INT_M1U;
-      if (res->number_chars_max < HOST_WIDE_INT_MAX
-	  && fmtres.range.max < HOST_WIDE_INT_MAX)
-	res->number_chars_max += fmtres.range.max;
+  if (!warned)
+    warned = maybe_warn (dirloc, pargrange, info, avail_range,
+			 fmtres.range, dir);
 
-      res->number_chars_min += fmtres.range.min;
-    }
+  /* Bump up the total maximum if it isn't too big.  */
+  if (res->range.max < HOST_WIDE_INT_MAX
+      && fmtres.range.max < HOST_WIDE_INT_MAX)
+    res->range.max += fmtres.range.max;
+
+  /* Raise the total unlikely maximum by the larger of the maximum
+     and the unlikely maximum.  It doesn't matter if the unlikely
+     maximum overflows.  */
+  if (fmtres.range.max < fmtres.range.unlikely)
+    res->range.unlikely += fmtres.range.unlikely;
   else
-    {
-      if (*dir.beg != '%')
-	{
-	  /* For plain character directives (i.e., the format string itself)
-	     but not others, point the caret at the first character that's
-	     past the end of the destination.  */
-	  dirloc.set_caret_index (offset + navail);
-	}
+    res->range.unlikely += fmtres.range.max;
 
-      if (!warned && fmtres.range.min > 0 && navail < fmtres.range.min)
-	{
-	  const char* fmtstr
-	    = (info.bounded
-	       ? (1 < fmtres.range.min
-		  ? G_("%<%.*s%> directive output truncated while writing "
-		       "%wu bytes into a region of size %wu")
-		  : G_("%<%.*s%> directive output truncated while writing "
-		       "%wu byte into a region of size %wu"))
-	       : (1 < fmtres.range.min
-		  ? G_("%<%.*s%> directive writing %wu bytes "
-		       "into a region of size %wu")
-		  : G_("%<%.*s%> directive writing %wu byte "
-		       "into a region of size %wu")));
-
-	  warned = fmtwarn (dirloc, pargrange, NULL,
-			    OPT_Wformat_length_, fmtstr,
-			    dirlen, dirbeg, fmtres.range.min,
-			    navail);
-	}
-      *res += fmtres.range.min;
-    }
+  res->range.min += fmtres.range.min;
+  res->range.likely += fmtres.range.likely;
 
   /* Has the minimum directive output length exceeded the maximum
      of 4095 bytes required to be supported?  */
   bool minunder4k = fmtres.range.min < 4096;
-  if (!minunder4k || fmtres.range.max > 4095)
+  bool maxunder4k = fmtres.range.max < 4096;
+  /* Clear UNDER4K in the overall result if the maximum has exceeded
+     the 4k (this is necessary to avoid the return valuye optimization
+     that may not be safe in the maximum case).  */
+  if (!maxunder4k)
     res->under4k = false;
 
-  if (!warned && 1 < warn_format_length
-      && (!minunder4k || fmtres.range.max > 4095))
+  if (!warned
+      /* Only warn at level 2.  */
+      && 1 < warn_format_length
+      && (!minunder4k
+	  || (!maxunder4k && fmtres.range.max < HOST_WIDE_INT_MAX)))
     {
       /* The directive output may be longer than the maximum required
 	 to be handled by an implementation according to 7.21.6.1, p15
@@ -2125,1 +2430,1 @@ format_directive (const pass_sprintf_length::call_info &info,
 	{

@@ Remove vestigial quoting.

 	  const char *fmtstr
 	    = (minunder4k
-	       ? G_("%<%.*s%> directive output between %qu and %wu "
+	       ? G_("%<%.*s%> directive output between %wu and %wu "
 		    "bytes may exceed minimum required size of 4095")
-	       : G_("%<%.*s%> directive output between %qu and %wu "
+	       : G_("%<%.*s%> directive output between %wu and %wu "
 		    "bytes exceeds minimum required size of 4095"));
 
 	  warned = fmtwarn (dirloc, pargrange, NULL,
@@ -2137,3 +2442,3 @@ format_directive (const pass_sprintf_length::call_info &info,
 	}
     }
 
@@ Handle LIKELY counter.

-  /* Has the minimum directive output length exceeded INT_MAX?  */
-  bool exceedmin = res->number_chars_min > target_int_max ();
+  /* Has the likely and maximum directive output exceeded INT_MAX?  */
+  bool likelyximax = *dirbeg && res->range.likely > target_int_max ();
+  bool maxximax = *dirbeg && res->range.max > target_int_max ();
 
   if (!warned
-      && (exceedmin
+      /* Warn for the likely output size at level 1.  */
+      && (likelyximax
+	  /* But only warn for the maximum at level 2.  */
 	  || (1 < warn_format_length
-	      && res->number_chars_max > target_int_max ())))
+	      && maxximax
+	      && fmtres.range.max < HOST_WIDE_INT_MAX)))
     {
       /* The directive output causes the total length of output
 	 to exceed INT_MAX bytes.  */
@@ -2157,7 +2466,7 @@ format_directive (const pass_sprintf_length::call_info &info,
       else
 	{
 	  const char *fmtstr
-	    = (exceedmin
+	    = (fmtres.range.min > target_int_max ()
 	       ? G_ ("%<%.*s%> directive output between %wu and %wu "
 		     "bytes causes result to exceed %<INT_MAX%>")
 	       : G_ ("%<%.*s%> directive output between %wu and %wu "
@@ -2169,6 +2478,16 @@ format_directive (const pass_sprintf_length::call_info &info,
 	}
     }
 
+  if (warned && fmtres.range.min < fmtres.range.likely
+      && fmtres.range.likely < fmtres.range.max)
+    {
+      inform (info.fmtloc,
+	      (1 == fmtres.range.likely
+	       ? G_("assuming directive output of %wu byte")
+	       : G_("assuming directive output of %wu bytes")),
+	      fmtres.range.likely);
+    }
+
   if (warned && fmtres.argmin)
     {
       if (fmtres.argmin == fmtres.argmax)
@@ -2184,3 +2503,3 @@ format_directive (const pass_sprintf_length::call_info &info,
 
   res->warned |= warned;
 

@@ Remove add_bytes.  It's completely subsumed by format_directive.

-  return true;
-}
-
-/* Account for the number of bytes between BEG and END (or between
-   BEG + strlen (BEG) when END is null) in the format string in a call
-   to a formatted output function described by INFO.  Reflect the count
-   in RES and issue warnings as appropriate.  */
-
-static void
-add_bytes (const pass_sprintf_length::call_info &info,
-	   const char *beg, const char *end, format_result *res)
-{
-  if (res->number_chars_min >= HOST_WIDE_INT_MAX)
-    return;
-
-  /* The number of bytes to output is the number of bytes between
-     the end of the last directive and the beginning of the next
-     one if it exists, otherwise the number of characters remaining
-     in the format string plus 1 for the terminating NUL.  */
-  size_t nbytes = end ? end - beg : strlen (beg) + 1;
-
-  /* Return if there are no bytes to add at this time but there are
-     directives remaining in the format string.  */
-  if (!nbytes)
-    return;
-
-  /* Compute the range of available bytes in the destination.  There
-     must always be at least one byte left for the terminating NUL
-     that's appended after the format string has been processed.  */
-  result_range avail_range = bytes_remaining (info.objsize, *res);
-
-  /* If issuing a diagnostic (only when one hasn't already been issued),
-     distinguish between a possible overflow ("may write") and a certain
-     overflow somewhere "past the end."  (Ditto for truncation.)
-     KNOWNRANGE is used to warn even at level 1 about possibly writing
-     past the end or truncation due to strings of unknown lengths that
-     are bounded by the arrays they are known to refer to.  */
-  if (!res->warned
-      && (avail_range.max < nbytes
-	  || ((res->knownrange || 1 < warn_format_length)
-	      && avail_range.min < nbytes)))
-    {
-      /* Set NAVAIL to the number of available bytes used to decide
-	 whether or not to issue a warning below.  The exact kind of
-	 warning will depend on AVAIL_RANGE.  */
-      unsigned HOST_WIDE_INT navail = avail_range.max;
-      if (nbytes <= navail && avail_range.min < HOST_WIDE_INT_MAX
-	  && (res->knownrange || 1 < warn_format_length))
-	navail = avail_range.min;
-
-      /* Compute the offset of the first format character that is beyond
-	 the end of the destination region and the length of the rest of
-	 the format string from that point on.  */
-      unsigned HOST_WIDE_INT off
-	= (unsigned HOST_WIDE_INT)(beg - info.fmtstr) + navail;
-
-      size_t len = strlen (info.fmtstr + off);
-
-      /* Create a location that underscores the substring of the format
-	 string that is or may be written past the end (or is or may be
-	 truncated), pointing the caret at the first character of the
-	 substring.  */
-      substring_loc loc
-	(info.fmtloc, TREE_TYPE (info.format), off, len ? off : 0,
-	 off + len - !!len);
-
-      /* Is the output of the last directive the result of the argument
-	 being within a range whose lower bound would fit in the buffer
-	 but the upper bound would not?  If so, use the word "may" to
-	 indicate that the overflow/truncation may (but need not) happen.  */
-      bool boundrange
-	= (res->number_chars_min < res->number_chars_max
-	   && res->number_chars_min + nbytes <= info.objsize);
-
-      if (!end && ((nbytes - navail) == 1 || boundrange))
-	{
-	  /* There is room for the rest of the format string but none
-	     for the terminating nul.  */
-	  const char *text
-	    = (info.bounded   // Snprintf and the like.
-	       ? (boundrange
-		  ? G_("output may be truncated before the last format character"
-		       : "output truncated before the last format character"))
-	       : (boundrange
-		  ? G_("may write a terminating nul past the end "
-		       "of the destination")
-		  : G_("writing a terminating nul past the end "
-		       "of the destination")));
-
-	  res->warned = fmtwarn (loc, NULL, NULL, OPT_Wformat_length_, text);
-	}
-      else
-	{
-	  /* There isn't enough room for 1 or more characters that remain
-	     to copy from the format string.  */
-	  const char *text
-	    = (info.bounded   // Snprintf and the like.
-	       ? (boundrange
-		  ? G_("output may be truncated at or before format character "
-		       "%qc at offset %wu")
-		  : G_("output truncated at format character %qc at offset %wu"))
-	       : (res->number_chars >= HOST_WIDE_INT_MAX
-		  ? G_("may write format character %#qc at offset %wu past "
-		       "the end of the destination")
-		  : G_("writing format character %#qc at offset %wu past "
-		       "the end of the destination")));
-
-	  res->warned = fmtwarn (loc, NULL, NULL, OPT_Wformat_length_,
-				 text, info.fmtstr[off], off);
-	}
-    }
-
-  if (res->warned && !end && info.objsize < HOST_WIDE_INT_MAX)
+  if (!dir.beg[0] && res->warned && info.objsize < HOST_WIDE_INT_MAX)
     {
       /* If a warning has been issued for buffer overflow or truncation
 	 (but not otherwise) help the user figure out how big a buffer
@@ -2304,76 +2511,30 @@ add_bytes (const pass_sprintf_length::call_info &info,
 
       location_t callloc = gimple_location (info.callstmt);
 
-      unsigned HOST_WIDE_INT min = res->number_chars_min;
-      unsigned HOST_WIDE_INT max = res->number_chars_max;
-      unsigned HOST_WIDE_INT exact
-	= (res->number_chars < HOST_WIDE_INT_MAX
-	   ? res->number_chars : res->number_chars_min);
+      unsigned HOST_WIDE_INT min = res->range.min;
+      unsigned HOST_WIDE_INT max = res->range.max;
 
-      if (min < max && max < HOST_WIDE_INT_MAX)
-	inform (callloc,
-		"%qE output between %wu and %wu bytes into "
-		"a destination of size %wu",
-		info.func, min + nbytes, max + nbytes, info.objsize);
-      else
+      if (min == max)
 	inform (callloc,
-		(nbytes + exact == 1
+		(min == 1
 		 ? G_("%qE output %wu byte into a destination of size %wu")
 		 : G_("%qE output %wu bytes into a destination of size %wu")),
-		info.func, nbytes + exact, info.objsize);
+		info.func, min, info.objsize);
+      else if (max < HOST_WIDE_INT_MAX)
+	inform (callloc,
+		"%qE output between %wu and %wu bytes into a destination "
+		"of size %wu",
+		info.func, min, max, info.objsize);
+      else if (min < res->range.likely && res->range.likely < max)
+	inform (callloc,
+		"%qE output %wu or more bytes (assuming %wu) into a destination "
+		"of size %wu",
+		info.func, min, res->range.likely, info.objsize);
+      else
+	inform (callloc,
+		"%qE output %wu or more bytes into a destination of size %wu",
+		info.func, min, info.objsize);
     }
 
-  /* Add the number of bytes and then check for INT_MAX overflow.  */
-  *res += nbytes;
-
-  /* Has the minimum output length minus the terminating nul exceeded
-     INT_MAX?  */
-  bool exceedmin = (res->number_chars_min - !end) > target_int_max ();
-
-  if (!res->warned
-      && (exceedmin
-	  || (1 < warn_format_length
-	      && (res->number_chars_max - !end) > target_int_max ())))
+  if (dump_file && *dir.beg)
     {
-      /* The function's output exceeds INT_MAX bytes.  */
-
-      /* Set NAVAIL to the number of available bytes used to decide
-	 whether or not to issue a warning below.  The exact kind of
-	 warning will depend on AVAIL_RANGE.  */
-      unsigned HOST_WIDE_INT navail = avail_range.max;
-      if (nbytes <= navail && avail_range.min < HOST_WIDE_INT_MAX
-	  && (res->bounded || 1 < warn_format_length))
-	navail = avail_range.min;
-
-      /* Compute the offset of the first format character that is beyond
-	 the end of the destination region and the length of the rest of
-	 the format string from that point on.  */
-      unsigned HOST_WIDE_INT off = (unsigned HOST_WIDE_INT)(beg - info.fmtstr);
-      if (navail < HOST_WIDE_INT_MAX)
-	off += navail;
-
-      size_t len = strlen (info.fmtstr + off);
-
-      substring_loc loc
-	(info.fmtloc, TREE_TYPE (info.format), off - !len, len ? off : 0,
-	 off + len - !!len);
-
-      if (res->number_chars_min == res->number_chars_max)
-	res->warned = fmtwarn (loc, NULL, NULL,
-			       OPT_Wformat_length_,
-			       "output of %wu bytes causes "
-			       "result to exceed %<INT_MAX%>",
-			       res->number_chars_min - !end);
-      else
-	{
-	  const char *text
-	    = (exceedmin
-	       ? G_ ("output between %wu and %wu bytes causes "
-		     "result to exceed %<INT_MAX%>")
-	       : G_ ("output between %wu and %wu bytes may cause "
-		     "result to exceed %<INT_MAX%>"));
-	  res->warned = fmtwarn (loc, NULL, NULL, OPT_Wformat_length_,
-				 text,
-				 res->number_chars_min - !end,
-				 res->number_chars_max - !end);
-	}

@@ Add debugging dump for each diretive.

+      fprintf (dump_file, "    Result: %lli, %lli, %lli, %lli "
+	       "(%lli, %lli, %lli, %lli)\n",
+	       (long long)fmtres.range.min,
+	       (long long)fmtres.range.likely,
+	       (long long)fmtres.range.max,
+	       (long long)fmtres.range.unlikely,
+	       (long long)res->range.min,
+	       (long long)res->range.likely,
+	       (long long)res->range.max,
+	       (long long)res->range.unlikely);
     }
+
+  return true;
 }
 
 #pragma GCC diagnostic pop
@@ -2390,0 +2563,0 @@ parse_directive (const pass_sprintf_length::call_info &info,

@@ Introduce parse_directive for clarity.  The code has been factored out
@@ of pass_sprintf_length::compute_format_length which has become much
@@ simpler as a result.  parse_directive populates struct DIRECTIVE and
@@ computes/extracts width and precision ranges.  The function needs
@@ a comment.  I have added it during the merge.

   const char *pcnt = strchr (str, '%');
   dir.beg = str;
 
-  if (!pcnt)
-    {
-      dir.len = strlen (str);
-      dir.fmtfunc = format_plain;
-      return dir.len;
-    }
-
-  if (size_t len = pcnt - str)
+  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 = format_plain;
-      return len;
+
+      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;
+  HOST_WIDE_INT width = -1;
+  HOST_WIDE_INT precision = -1;
+
+  tree star_width = NULL_TREE;
+  tree star_precision = NULL_TREE;
 
   if (ISDIGIT (*pf))
     {
@@ -2416,8 +2599,7 @@ parse_directive (const pass_sprintf_length::call_info &info,
 	 width and sort it out later after the next character has
 	 been seen.  */
       char *end;
-      dir.width = strtol (pf, &end, 10);
-      dir.have_width = true;
+      width = strtol (pf, &end, 10);
       pf = end;
     }
   else if ('*' == *pf)
@@ -2427,7 +2609,7 @@ parse_directive (const pass_sprintf_length::call_info &info,
       if (gimple_call_num_args (info.callstmt) <= *argno)
 	return 0;
 
-      dir.star_width = gimple_call_arg (info.callstmt, (*argno)++);
+      star_width = gimple_call_arg (info.callstmt, (*argno)++);
       ++pf;
     }
 
@@ -2435,11 +2617,11 @@ parse_directive (const pass_sprintf_length::call_info &info,
     {
       /* Handle the POSIX dollar sign which references the 1-based
 	 positional argument number.  */
-      if (dir.have_width)
-	dollar = dir.width + info.argidx;
-      else if (dir.star_width
-	       && TREE_CODE (dir.star_width) == INTEGER_CST)
-	dollar = dir.width + tree_to_shwi (dir.star_width);
+      if (width != -1)
+	dollar = width + info.argidx;
+      else if (star_width
+	       && TREE_CODE (star_width) == INTEGER_CST)
+	dollar = width + tree_to_shwi (star_width);
 
       /* Bail when the numbered argument is out of range (it will
 	 have already been diagnosed by -Wformat).  */
@@ -2450,21 +2632,21 @@ parse_directive (const pass_sprintf_length::call_info &info,
 
       --dollar;
 
-      dir.star_width = NULL_TREE;
-      dir.have_width = false;
+      star_width = NULL_TREE;
+      width = -1;
       ++pf;
     }
 
-  if (dollar || !dir.star_width)
+  if (dollar || !star_width)
     {
-      if (dir.have_width)
+      if (width != -1)
 	{
-	  if (dir.width == 0)
+	  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.  */
-	      dir.have_width = false;
+	      width = -1;
 	      dir.set_flag ('0');
 	    }
 	  else if (!dollar)
@@ -2497,13 +2679,12 @@ parse_directive (const pass_sprintf_length::call_info &info,
       if (ISDIGIT (*pf))
 	{
 	  char *end;
-	  dir.width = strtol (pf, &end, 10);
-	  dir.have_width = true;
+	  width = strtol (pf, &end, 10);
 	  pf = end;
 	}
       else if ('*' == *pf)
 	{
-	  dir.star_width = gimple_call_arg (info.callstmt, (*argno)++);
+	  star_width = gimple_call_arg (info.callstmt, (*argno)++);
 	  ++pf;
 	}
       else if ('\'' == *pf)
@@ -2525,21 +2706,19 @@ parse_directive (const pass_sprintf_length::call_info &info,
       if (ISDIGIT (*pf))
 	{
 	  char *end;
-	  dir.precision = strtol (pf, &end, 10);
-	  dir.have_precision = true;
+	  precision = strtol (pf, &end, 10);
 	  pf = end;
 	}
       else if ('*' == *pf)
 	{
-	  dir.star_precision = gimple_call_arg (info.callstmt, (*argno)++);
+	  star_precision = gimple_call_arg (info.callstmt, (*argno)++);
 	  ++pf;
 	}
       else
 	{
 	  /* The decimal precision or the asterisk are optional.
 	     When neither is dirified it's taken to be zero.  */
-	  dir.precision = 0;
-	  dir.have_precision = true;
+	  precision = 0;
 	}
     }
 
@@ -2594,7 +2773,7 @@ parse_directive (const pass_sprintf_length::call_info &info,
 	 undefined prevent the result from being folded.  */
     case '\0':
       --pf;
-      res->bounded = false;
+      res->range.min = res->range.max = HOST_WIDE_INT_M1U;
       /* FALLTHRU */
     case '%':
       dir.fmtfunc = format_percent;
@@ -2632,6 +2811,9 @@ parse_directive (const pass_sprintf_length::call_info &info,
       break;
 
     case 'c':
+      dir.fmtfunc = format_character;
+      break;
+
     case 'S':
     case 's':
       dir.fmtfunc = format_string;
@@ -2644,3 +2826,3 @@ parse_directive (const pass_sprintf_length::call_info &info,
 
   dir.specifier = *pf++;
 
@@ Set width and precision value or range for each directive here.

+  if (star_width)
+    {
+      if (TREE_CODE (TREE_TYPE (star_width)) != INTEGER_TYPE)
+	return 0;
+
+      dir.set_width (star_width);
+    }
+  else
+    dir.set_width (width);
+
+  if (star_precision)
+    {
+      if (TREE_CODE (TREE_TYPE (star_precision)) != INTEGER_TYPE)
+	return 0;
+
+      dir.set_precision (star_precision);
+    }
+  else
+    dir.set_precision (precision);
+
   /* 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
@@ -2654,3 +2856,3 @@ parse_directive (const pass_sprintf_length::call_info &info,
 
   /* Return the length of the format directive.  */
   dir.len = pf - pcnt;

@@ Add debugging dump for each directive.

+
+  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)
+	{
+	  if (dir.width[0] == dir.width[1])
+	    fprintf (dump_file, ", width = %lli", (long long)dir.width[0]);
+	  else
+	    fprintf (dump_file, ", width in range [%lli, %lli]",
+		     (long long)dir.width[0], (long long)dir.width[1]);
+	}
+
+      if (star_precision)
+	{
+	  if (dir.prec[0] == dir.prec[1])
+	    fprintf (dump_file, ", precision = %lli", (long long)dir.prec[0]);
+	  else
+	    fprintf (dump_file, ", precision in range [%lli, %lli]",
+		     (long long)dir.prec[0], (long long)dir.prec[1]);
+	}
+      fputc ('\n', dump_file);
+    }
+
   return dir.len;
 }
 
@@ -2668,40 +2896,56 @@ bool
 pass_sprintf_length::compute_format_length (const call_info &info,
 					    format_result *res)
 {
+  if (dump_file)
+    {
+      location_t callloc = gimple_location (info.callstmt);
+      fprintf (dump_file, "%s:%i: ",
+	       LOCATION_FILE (callloc), LOCATION_LINE (callloc));
+      print_generic_expr (dump_file, info.func, dump_flags);
+
+      fprintf (dump_file, ": objsize = %llu, fmtstr = \"%s\"\n",
+	       (unsigned long long)info.objsize, info.fmtstr);
+    }
+
   /* Reset exact, minimum, and maximum character counters.  */
-  res->number_chars = res->number_chars_min = res->number_chars_max = 0;
+  res->range.min = res->range.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;
 
-  const char *pf;
-  for (pf = info.fmtstr; *pf; )
+  for (const char *pf = info.fmtstr; ; ++dirno)
     {
       directive dir = directive ();
+      dir.dirno = dirno;
 
       size_t n = parse_directive (info, dir, res, pf, &argno);
-      if (!n)
-	return false;
-
-      pf += n;
 
+      /* Return failure if the format function fails.  */
       if (!format_directive (info, res, dir))
 	return false;
-    }
 
-  add_bytes (info, pf, 0, res);
+      /* 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';
+
+      pf += n;
+    }
 
-  /* Complete format string was processed (with or without warnings).  */
+  /* The complete format string was processed (with or without warnings).  */
   return true;
 }
 
@@ -2736,2 +2980,2 @@ try_substitute_return_value (gimple_stmt_iterator *gsi,
 			     const format_result &res)
 {

@@ Simplify the function a bit.  No functional changes.

   tree lhs = gimple_get_lhs (info.callstmt);
+  if (!lhs)
+    return;
+
+  unsigned HOST_WIDE_INT minretval = res.range.min;
+
+  /* The maximum return value is in most cases bounded by RES.RANGE.MAX
+     but in cases involving multibyte characters could be as large as
+     RES.RANGE.UNLIKELY.  */
+  unsigned HOST_WIDE_INT maxretval
+    = res.range.unlikely < res.range.max ? res.range.max : res.range.unlikely;
+
+  /* Adjust the number of bytes which includes the terminating nul
+     to reflect the return value of the function which does not.
+     Because the valid range of the function is [INT_MIN, INT_MAX],
+     a valid range before the adjustment below is [0, INT_MAX + 1]
+     (the functions only return negative values on error or undefined
+     behavior).  */
+  if (minretval <= target_int_max () + 1)
+    --minretval;
+  if (maxretval <= target_int_max () + 1)
+    --maxretval;
 
   /* Avoid the return value optimization when the behavior of the call
      is undefined either because any directive may have produced 4K or
@@ -2743,18 +3008,18 @@ try_substitute_return_value (gimple_stmt_iterator *gsi,
      the output overflows the destination object (but leave it enabled
      when the function is bounded because then the behavior is well-
      defined).  */
-  if (lhs && res.bounded && res.under4k
-      && (info.bounded || res.number_chars <= info.objsize)
-      && res.number_chars - 1 <= target_int_max ())
+  if (res.under4k
+      && minretval == maxretval
+      && (info.bounded || minretval < info.objsize)
+      && minretval <= target_int_max ())
     {
-      tree cst = build_int_cst (integer_type_node, res.number_chars - 1);
+      tree cst = build_int_cst (integer_type_node, minretval);
 
       if (info.nowrite)
 	{
 	  /* Replace the call to the bounded function with a zero size
 	     (e.g., snprintf(0, 0, "%i", 123) with the constant result
-	     of the function minus 1 for the terminating NUL which
-	     the function's  return value does not include.  */
+	     of the function.  */
 	  if (!update_call_from_tree (gsi, cst))
 	    gimplify_and_update_call_from_tree (gsi, cst);
 	  gimple *callstmt = gsi_stmt (*gsi);
@@ -2763,8 +3028,7 @@ try_substitute_return_value (gimple_stmt_iterator *gsi,
       else
 	{
 	  /* Replace the left-hand side of the call with the constant
-	     result of the formatted function minus 1 for the terminating
-	     NUL which the function's return value does not include.  */
+	     result of the formatted function.  */
 	  gimple_call_set_lhs (info.callstmt, NULL_TREE);
 	  gimple *g = gimple_build_assign (lhs, cst);
 	  gsi_insert_after (gsi, g, GSI_NEW_STMT);
@@ -2773,72 +3037,57 @@ try_substitute_return_value (gimple_stmt_iterator *gsi,
 
       if (dump_file)
 	{
-	  location_t callloc = gimple_location (info.callstmt);
-	  fprintf (dump_file, "On line %i substituting ",
-		   LOCATION_LINE (callloc));
+	  fprintf (dump_file, "  Substituting %llu",
+		   (unsigned long long)info.objsize);
 	  print_generic_expr (dump_file, cst, dump_flags);
-	  fprintf (dump_file, " for ");
-	  print_generic_expr (dump_file, info.func, dump_flags);
-	  fprintf (dump_file, " %s (output %s).\n",
-		   info.nowrite ? "call" : "return value",
-		   res.constant ? "constant" : "variable");
+	  fprintf (dump_file, " for %s.\n",
+		   info.nowrite ? "statement" : "return value");
 	}
     }
   else
     {
-      unsigned HOST_WIDE_INT maxbytes;
+      bool setrange = false;
 
-      if (lhs
-	  && res.bounded
-	  && ((maxbytes = res.number_chars - 1) <= target_int_max ()
-	      || (res.number_chars_min - 1 <= target_int_max ()
-		  && (maxbytes = res.number_chars_max - 1) <= target_int_max ()))
-	  && (info.bounded || maxbytes < info.objsize))
+      if ((info.bounded || maxretval < info.objsize)
+	  && res.under4k
+	  && (minretval < target_int_max ()
+	      && maxretval < target_int_max ()))
 	{
 	  /* If the result is in a valid range bounded by the size of
 	     the destination set it so that it can be used for subsequent
 	     optimizations.  */
 	  int prec = TYPE_PRECISION (integer_type_node);
 
-	  if (res.number_chars < target_int_max () && res.under4k)
-	    {
-	      wide_int num = wi::shwi (res.number_chars - 1, prec);
-	      set_range_info (lhs, VR_RANGE, num, num);
-	    }
-	  else if (res.number_chars_min < target_int_max ()
-		   && res.number_chars_max < target_int_max ())
-	    {
-	      wide_int min = wi::shwi (res.under4k ? res.number_chars_min - 1
-				       : target_int_min (), prec);
-	      wide_int max = wi::shwi (res.number_chars_max - 1, prec);
-	      set_range_info (lhs, VR_RANGE, min, max);
-	    }
+	  wide_int min = wi::shwi (minretval, prec);
+	  wide_int max = wi::shwi (maxretval, prec);
+	  set_range_info (lhs, VR_RANGE, min, max);
+
+	  setrange = true;
 	}
 
       if (dump_file)
 	{
 	  const char *inbounds
-	    = (res.number_chars_min <= info.objsize
-	       ? (res.number_chars_max <= info.objsize
+	    = (minretval <= info.objsize
+	       ? (maxretval <= info.objsize
 		  ? "in" : "potentially out-of")
 	       : "out-of");
 
-	  location_t callloc = gimple_location (info.callstmt);
-	  fprintf (dump_file, "On line %i ", LOCATION_LINE (callloc));
-	  print_generic_expr (dump_file, info.func, dump_flags);
-
-	  const char *ign = lhs ? "" : " ignored";
-	  if (res.number_chars >= HOST_WIDE_INT_MAX)
+	  const char *what = setrange ? "Setting" : "Discarding";
+	  if (minretval != maxretval)
 	    fprintf (dump_file,
-		     " %s-bounds return value in range [%lu, %lu]%s.\n",
-		     inbounds,
-		     (unsigned long)res.number_chars_min,
-		     (unsigned long)res.number_chars_max, ign);
+		     "  %s %s-bounds return value range [%llu, %llu].\n",
+		     what, inbounds,
+		     (unsigned long long)minretval,
+		     (unsigned long long)maxretval);
 	  else
-	    fprintf (dump_file, " %s-bounds return value %lu%s.\n",
-		     inbounds, (unsigned long)res.number_chars, ign);
+	    fprintf (dump_file, "  %s %s-bounds return value %llu.\n",
+		     what, inbounds, (unsigned long long)minretval);
 	}
     }
+
+  if (dump_file)
+    fputc ('\n', dump_file);
 }
 
 /* Determine if a GIMPLE CALL is to one of the sprintf-like built-in
diff --git a/gcc/substring-locations.c b/gcc/substring-locations.c
index 60bf1b0..23b94fc 100644
--- a/gcc/substring-locations.c
+++ b/gcc/substring-locations.c
@@ -191,0 +191,0 @@ const char *

@@ This spurious change was removed during the merge.

 substring_loc::get_location (location_t *out_loc) const
 {
   gcc_assert (out_loc);
+  if (m_caret_idx < 0)
+    return "m_caret_idx < 0";
+
+  if (m_start_idx < 0)
+    return "m_start_idx < 0";
+
+  if (m_end_idx < 0)
+    return "m_end_idx < 0";
+
   return lang_hooks.get_substring_location (*this, out_loc);
 }
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-2.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-2.c
index 260f4fc..b873a0c 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-2.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-2.c
@@ -59,15 +59,15 @@ void must_not_eliminate (void);
 
 typedef __SIZE_TYPE__ size_t;
 
-extern int i;
-extern unsigned u;
-extern long li;
-extern unsigned long lu;
-extern size_t sz;
-extern char *str;
+volatile int i;
+volatile unsigned u;
+volatile long li;
+volatile unsigned long lu;
+volatile size_t sz;
+volatile char *str;
 
-extern double d;
-extern long double ld;
+volatile double d;
+volatile long double ld;
 
 /* Verify that overflowing the destination object disables the return
    value optimization.  */
@@ -280,7 +280,8 @@ RNG (0,  6,   8, "%s%ls", "1", L"2");
 
 */
 
-/*  Only conditional calls to abort should be made (with any probability):
+/*  Only conditional calls to must_not_eliminate must be made (with
+    any probability):
     { dg-final { scan-tree-dump-times "> \\\[\[0-9.\]+%\\\]:\n *must_not_eliminate" 124 "optimized" { target { ilp32 || lp64 } } } }
     { dg-final { scan-tree-dump-times "> \\\[\[0-9.\]+%\\\]:\n *must_not_eliminate" 93 "optimized" { target { { ! ilp32 } && { ! lp64 } } } } }
     No unconditional calls to abort should be made:
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-5.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-5.c
index c4489ac..abffcae 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-5.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-5.c
@@ -135,7 +135,6 @@ void test_arg_string (const char *s)
 
 void test_arg_multiarg (int i, double d)
 {
-  EQL (16, "%i %f %s", 123, 3.14, "abc");
   EQL (16, "%12i %s", i, "abc");
   EQL (16, "%*i %s", 12, i, "abc");
 }
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c
index aca11e5..e34f0bc 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c
@@ -121,19 +121,21 @@ void test_sprintf_flexarray (void *p, int i)
   struct S
   {
     int n;
-    char a [];
+    char a[];
   } *s = p;
 
-  __builtin_sprintf (s->a, "%c",       'x');
+  FUNC (sprintf)(s->a, "%c",       'x');
 
-  __builtin_sprintf (s->a, "%s",       "");
-  __builtin_sprintf (s->a, "%s",       "abc");
-  __builtin_sprintf (s->a, "abc%sghi", "def");
+  FUNC (sprintf)(s->a, "%-s",      "");
+  FUNC (sprintf)(s->a, "%-s",      "abc");
+  FUNC (sprintf)(s->a, "abc%sghi", "def");
 
-  __builtin_sprintf (s->a, "%i",       1234);
+  FUNC (sprintf)(s->a, "%i",       1234);
 
-  __builtin_sprintf (buffer (1), "%s",  s->a);
-  __builtin_sprintf (buffer (1), "%s",  s [i].a);
+  FUNC (sprintf)(buffer (1), "%-s", s->a);      /* { dg-warning "may write a terminating nul past the end" } */
+  FUNC (sprintf)(buffer (1), "%-s", s [i].a);   /* { dg-warning "may write a terminating nul past the end" } */
+  FUNC (sprintf)(buffer (2), "%-s", s->a);
+  FUNC (sprintf)(buffer (2), "%-s", s [i].a);
 }
 
 /* Same as above but for zero-length arrays.  */
@@ -146,16 +148,16 @@ void test_sprintf_zero_length_array (void *p, int i)
     char a [0];
   } *s = p;
 
-  __builtin_sprintf (s->a, "%c",       'x');
+  FUNC (sprintf)(s->a, "%c",       'x');
 
-  __builtin_sprintf (s->a, "%s",       "");
-  __builtin_sprintf (s->a, "%s",       "abc");
-  __builtin_sprintf (s->a, "abc%sghi", "def");
+  FUNC (sprintf)(s->a, "%s",       "");
+  FUNC (sprintf)(s->a, "%s",       "abc");
+  FUNC (sprintf)(s->a, "abc%sghi", "def");
 
-  __builtin_sprintf (s->a, "%i",       1234);
+  FUNC (sprintf)(s->a, "%i",       1234);
 
-  __builtin_sprintf (buffer (1), "%s",  s->a);
-  __builtin_sprintf (buffer (1), "%s",  s [i].a);
+  FUNC (sprintf)(buffer (1), "%s",  s->a);
+  FUNC (sprintf)(buffer (1), "%s",  s [i].a);
 }
 
 #undef T
@@ -298,6 +300,7 @@ void test_sprintf_chk_s_const (void)
 
   T (2, "_%s",   "");
   T (2, "%%%s",  "");
+  T (2, "%%%%%s",  "");         /* { dg-warning "nul past the end" } */
   T (2, "%s%%",  "");
   T (2, "_%s",   "1");          /* { dg-warning "nul past the end" } */
   T (2, "%%%s",  "1");          /* { dg-warning "nul past the end" } */
@@ -329,7 +332,10 @@ void test_sprintf_chk_s_const (void)
   T (1, "%*ls",  0, L"\0");
   T (1, "%*ls",  1, L"");       /* { dg-warning "nul past the end" } */
 
-  T (1, "%ls",      L"1");      /* { dg-warning "nul past the end" } */
+  /* A wide character string need not convert into any bytes (although
+     individual ASCII characters are assumed to convert into 1 bt %lc
+     so this could be made smarter.  */
+  T (1, "%ls",      L"1");      /* { dg-warning "directive writing up to 6 bytes into a region of size 1" } */
   T (1, "%.0ls",    L"1");
   T (2, "%.0ls",    L"1");
   T (2, "%.1ls",    L"1");
@@ -345,9 +351,9 @@ void test_sprintf_chk_s_const (void)
   T (3, "%.0ls",    L"1");
   T (3, "%.1ls",    L"1");
   T (3, "%.2ls",    L"1");
-  T (3, "%ls",      L"12");
+  T (3, "%ls",      L"12");     /* { dg-warning "directive writing up to 12 bytes" } */
 
-  T (3, "%ls",      L"123");    /* { dg-warning "nul past the end" } */
+  T (3, "%ls",      L"123");    /* { dg-warning "directive writing up to 18 bytes" } */
   T (3, "%.0ls",    L"123");
   T (3, "%.1ls",    L"123");
   T (3, "%.2ls",    L"123");
@@ -954,16 +960,20 @@ void test_sprintf_chk_a_const (void)
 
   /* The least number of bytes on output is 6 for "0x0p+0".  When precision
      is missing the number of digits after the decimal point isn't fully
-     specified by C (it seems like a defect).  */
-  T (0, "%a",   0.0);         /* { dg-warning "into a region" } */
-  T (0, "%la",  0.0);         /* { dg-warning "into a region" } */
-  T (1, "%a",   0.0);         /* { dg-warning "into a region" } */
-  T (2, "%a",   0.0);         /* { dg-warning "into a region" } */
-  T (3, "%a",   0.0);         /* { dg-warning "into a region" } */
-  T (4, "%a",   0.0);         /* { dg-warning "into a region" } */
-  T (5, "%a",   0.0);         /* { dg-warning "into a region" } */
-  T (6, "%a",   0.0);         /* { dg-warning "writing a terminating nul" } */
-  T (7, "%a",   0.0);
+     specified by C (a defect).  Two sets of implementations are known to
+     exist: those that trim trailing zeros (e.g., Glibc) and those that
+     pad output with trailing zeros so that all floating point numbers
+     result in the same number of bytes on output (e.g., Solaris).  */
+  T ( 0, "%a",   0.0);         /* { dg-warning "writing between 6 and 20 bytes" } */
+  T ( 0, "%la",  0.0);         /* { dg-warning "between 6 and 20 bytes" } */
+  T ( 1, "%a",   0.0);         /* { dg-warning "between 6 and 20 bytes" } */
+  T ( 2, "%a",   0.0);         /* { dg-warning "between 6 and 20 bytes" } */
+  T ( 3, "%a",   0.0);         /* { dg-warning "between 6 and 20 bytes" } */
+  T ( 4, "%a",   0.0);         /* { dg-warning "between 6 and 20 bytes" } */
+  T ( 5, "%a",   0.0);         /* { dg-warning "between 6 and 20 bytes" } */
+  T ( 6, "%a",   0.0);         /* { dg-warning "between 6 and 20 bytes" } */
+  T (19, "%a",   0.0);         /* { dg-warning "between 6 and 20 bytes" } */
+  T (20, "%a",   0.0);         /* { dg-warning "may write a terminating nul" } */
 
   T (0, "%.a",    0.0);       /* { dg-warning "into a region" } */
   T (0, "%.0a",   0.0);       /* { dg-warning "into a region" } */
@@ -1020,24 +1030,21 @@ void test_sprintf_chk_e_const (void)
   T ( 6, "%.e",  1.0);
   T ( 6, "%.0e", 1.0);
 
-  /* The actual output of the following directives depends on the rounding
-     mode.  Verify that the warning correctly reflects that.  At level 1,
-     since the minimum number of bytes output by the directive fits the
-     space the directive itself isn't diagnosed but the terminating nul
-     is.  The directive is diagnosed at level 2.  */
-  T (12, "%e",  9.999999e+99);  /* { dg-warning "terminating nul" } */
-  T (12, "%e",  9.9999994e+99); /* { dg-warning "terminating nul" } */
-  T (12, "%e",  9.9999995e+99); /* { dg-warning "terminating nul" } */
-  T (12, "%e",  9.9999996e+99); /* { dg-warning "terminating nul" } */
-  T (12, "%e",  9.9999997e+99); /* { dg-warning "terminating nul" } */
-  T (12, "%e",  9.9999998e+99); /* { dg-warning "terminating nul" } */
-
-  T (12, "%Le", 9.9999994e+99L);/* { dg-warning "terminating nul" } */
-  T (12, "%Le", 9.9999995e+99L);/* { dg-warning "terminating nul" } */
-  T (12, "%Le", 9.9999996e+99L);/* { dg-warning "terminating nul" } */
-  T (12, "%Le", 9.9999997e+99L);/* { dg-warning "terminating nul" } */
-  T (12, "%Le", 9.9999998e+99L);/* { dg-warning "terminating nul" } */
-  T (12, "%Le", 9.9999999e+99L);/* { dg-warning "terminating nul" } */
+  /* The output of the following directives depends on the rounding
+     mode.  */
+  T (12, "%e",  9.999999e+99);  /* { dg-warning "between 12 and 13" } */
+  T (12, "%e",  9.9999994e+99); /* { dg-warning "between 12 and 13" } */
+  T (12, "%e",  9.9999995e+99); /* { dg-warning "between 12 and 13" } */
+  T (12, "%e",  9.9999996e+99); /* { dg-warning "between 12 and 13" } */
+  T (12, "%e",  9.9999997e+99); /* { dg-warning "between 12 and 13" } */
+  T (12, "%e",  9.9999998e+99); /* { dg-warning "between 12 and 13" } */
+
+  T (12, "%Le", 9.9999994e+99L);/* { dg-warning "between 12 and 13" } */
+  T (12, "%Le", 9.9999995e+99L);/* { dg-warning "between 12 and 13" } */
+  T (12, "%Le", 9.9999996e+99L);/* { dg-warning "between 12 and 13" } */
+  T (12, "%Le", 9.9999997e+99L);/* { dg-warning "between 12 and 13" } */
+  T (12, "%Le", 9.9999998e+99L);/* { dg-warning "between 12 and 13" } */
+  T (12, "%Le", 9.9999999e+99L);/* { dg-warning "between 12 and 13" } */
 }
 
 /* At -Wformat-length level 1 unknown numbers are assumed to have
@@ -1047,21 +1054,32 @@ void test_sprintf_chk_e_const (void)
 void test_sprintf_chk_s_nonconst (int w, int p, const char *s)
 {
   T (-1, "%s",   s);
-  T ( 0, "%s",   s);            /* { dg-warning "nul past the end" } */
-  T ( 1, "%s",   s);
+  T ( 0, "%s",   s);            /* { dg-warning "writing likely 1 or more bytes" } */
+  T ( 1, "%s",   s);            /* { dg-warning "may write a terminating nul" } */
+  T (-1, "%.0s", s);
   T ( 1, "%.0s", s);
-  T ( 1, "%.1s", s);            /* { dg-warning "nul past the end" } */
+  T (-1, "%.1s", s);
+  T ( 1, "%.1s", s);            /* { dg-warning "may write a terminating nul past the end" } */
+  T (-1, "%.2s", s);
+  T ( 1, "%.2s", s);            /* { dg-warning "directive writing up to 2 bytes" } */
+  T ( 2, "%.2s", s);            /* { dg-warning "may write a terminating nul" } */
+  T ( 3, "%.2s", s);
 
   /* The string argument is constant but the width and/or precision
      is not.  */
-  T ( 1, "%*s",  w, "");
-  T ( 1, "%*s",  w, "1");       /* { dg-warning "nul past the end" } */
+  T (-1, "%*s",  w, "");
+  T ( 1, "%*s",  w, "");        /* { dg-warning "may write a terminating nul past the end" } */
+  T (-1, "%*s",  w, "1");
+  T ( 1, "%*s",  w, "1");       /* { dg-warning "writing a terminating nul past the end" } */
+  T (-1, "%.*s", p, "");
   T ( 1, "%.*s", p, "");
+  T (-1, "%.*s", p, "1");
   T ( 1, "%.*s", p, "1");       /* { dg-warning "may write a terminating nul" } */
-  T ( 1, "%.*s", w, "123");     /* { dg-warning "writing between 0 and 3 bytes into a region of size 1" } */
+  T ( 1, "%.*s", w, "123");     /* { dg-warning "writing up to 3 bytes into a region of size 1" } */
 
-  T ( 1, "%*s", w, "123");      /* { dg-warning "writing 3 or more bytes into a region of size 1" } */
-  T ( 2, "%*s", w, "123");      /* { dg-warning "writing 3 or more bytes into a region of size 2" } */
+  /* Either of the messages below is acceptable.  */
+  T ( 1, "%*s", w, "123");      /* { dg-warning "writing 3 or more bytes into a region of size 1|writing between 3 and 2147483648 bytes" } */
+  T ( 2, "%*s", w, "123");      /* { dg-warning "writing 3 or more bytes into a region of size 2|writing between 3 and 2147483648 bytes" } */
   T ( 3, "%*s", w, "123");      /* { dg-warning "writing a terminating nul past the end" } */
   T ( 4, "%*s", w, "123");
 
@@ -1071,6 +1089,13 @@ void test_sprintf_chk_s_nonconst (int w, int p, const char *s)
      the end (we don't print "past the end" when we're not
      sure which we can't be with an unknown string.  */
   T (1, "%1s",  s);             /* { dg-warning "writing a terminating nul past the end" } */
+
+  /* Multiple directives.  */
+  T (1, "%s%-s",    s, s);   /* { dg-warning ".%-s. directive writing likely 1 or more bytes" } */
+  T (1, "%s%-s%s", s, s, s); /* { dg-warning ".%-s. directive writing likely 1 or more bytes" } */
+  T (1, "%1s%-s%s", s, s, s); /* { dg-warning ".%-s. directive writing likely 1 or more bytes into a region of size 0" } */
+  T (1, "%s%1s%s", s, s, s); /* { dg-warning ".%1s. directive writing 1 or more bytes into a region of size between 0 and 1" } */
+  T (1, "%s%-s%1s", s, s, s); /* { dg-warning ".%-s. directive writing likely 1 or more bytes into a region of size between 0 and 1" } */
 }
 
 /* Exercise the hh length modifier with all integer specifiers and
@@ -1179,15 +1204,15 @@ void test_sprintf_chk_hh_nonconst (int w, int p, int a)
   /* The argument is known but precision isn't.  When the argument
      is zero only the first call can be diagnosed since a zero
      precision would result in no bytes on output.  */
-  T (0, "%.*hhi", p,   0);      /* { dg-warning "nul past the end" } */
-  T (1, "%.*hhi", p,   0);
+  T (0, "%.*hhi", p,   0);      /* { dg-warning " writing up to \[0-9\]+ bytes" } */
+  T (1, "%.*hhi", p,   0);      /* { dg-warning "may write a terminating nul" }*/
   T (2, "%.*hhi", p,   0);
   T (2, "%.*hhi", p,  12);      /* { dg-warning "nul past the end" } */
   T (2, "%.*hhi", p, 123);      /* { dg-warning "into a region" } */
 
   /* The argument is known but neither width nor precision is.  */
-  T (0, "%*.*hhi", w, p,   0);  /* { dg-warning "nul past the end" } */
-  T (1, "%*.*hhi", w, p,   0);
+  T (0, "%*.*hhi", w, p,   0);  /* { dg-warning "writing up to \[0-9\]+ bytes" } */
+  T (1, "%*.*hhi", w, p,   0);  /* { dg-warning "may write a terminating nul" } */
   T (2, "%*.*hhi", w, p,   0);
   T (2, "%*.*hhi", w, p,  12);  /* { dg-warning "nul past the end" } */
   T (2, "%*.*hhi", w, p, 123);  /* { dg-warning "into a region" } */
@@ -1310,7 +1335,8 @@ void test_sprintf_chk_int_nonconst (int w, int p, int a)
   T (3, "%2u",          a);
   T (3, "%2x",          a);
 
-  T (1, "%.*d",      p, a);
+  T (1, "%.*d",      p, a);     /* { dg-warning "nul past the end" } */
+  T (2, "%.*d",      p, a);
 
   T (4, "%i %i",        a, a);
   /* The following will definitely be "writing a terminating nul past the end"
@@ -1359,7 +1385,7 @@ void test_sprintf_chk_e_nonconst (int w, int p, double d)
   T ( 8, "%.1e",        d);
 
   T ( 0, "%*e",      0, d);           /* { dg-warning "writing between 12 and 14 bytes into a region of size 0" } */
-  T ( 0, "%*e",      w, d);           /* { dg-warning "writing 12 or more bytes into a region of size 0" } */
+  T ( 0, "%*e",      w, d);           /* { dg-warning "writing 12 or more bytes into a region of size 0|writing between 12 and \[0-9\]+ bytes into a region of size 0" } */
 }
 
 void test_sprintf_chk_f_nonconst (double d)
@@ -1412,23 +1438,22 @@ void test_vsprintf_chk_c (va_list va)
   T (3, "%c%c");
 
   /* Wide characters.  */
-  T (0, "%lc");             /* { dg-warning "nul past the end" } */
-  T (1, "%lc");
-  T (2, "%lc");
+  T (0, "%lc");             /* { dg-warning "up to 6 bytes " } */
+  T (1, "%lc");             /* { dg-warning "up to 6 bytes " } */
+  T (2, "%lc");             /* { dg-warning "may write a terminating nul" } */
 
   /* The following could result in as few as a single byte and in as many
      as MB_CUR_MAX, but since the MB_CUR_MAX value is a runtime property
      the write cannot be reliably diagnosed.  */
-  T (2, "%lc");
-  T (2, "%1lc");
+  T (2, "%1lc");            /* { dg-warning "may write a terminating nul" } */
   /* Writing some unknown number of bytes into a field two characters wide.  */
   T (2, "%2lc");            /* { dg-warning "nul past the end" } */
-  T (2, "%lc%lc");
+  T (2, "%lc%lc");          /* { dg-warning "writing up to 6 bytes into a region of size between 0 and 2" } */
 
-  T (3, "%lc%c");
+  T (3, "%lc%c");           /* { dg-warning "may write a terminating nul" } */
   /* Here in the best case each argument will format as single character,
      causing the terminating NUL to be written past the end.  */
-  T (3, "%lc%c%c");
+  T (3, "%lc%c%c");         /* { dg-warning "writing 1 byte into a region of size between 0 and 2" } */
 }
 
 void test_vsprintf_chk_int (va_list va)
@@ -1512,11 +1537,11 @@ void test_snprintf_c_const (char *d)
   T (2, "%2lc", (wint_t)L'1');          /* { dg-warning "output truncated before the last format character" } */
 
   T (3, "%lc%c",   (wint_t)'1', '2');
-  /* Here in the best case each argument will format as single character,
-     causing the output to be truncated just before the terminating NUL
-     (i.e., cutting off the '3').  */
-  T (3, "%lc%c%c", (wint_t)'1', '2', '3');   /* { dg-warning "output truncated" } */
-  T (3, "%lc%lc%c", (wint_t)'1', (wint_t)'2', '3'); /* { dg-warning "output truncated" } */
+  /* Here %lc may result in anywhere between 0 and MB_CUR_MAX characters
+     so the minimum number of bytes on output is 2 (plus the terminating
+     nul), but the likely number is 3 (plus the nul).  */
+  T (3, "%lc%c%c", (wint_t)'1', '2', '3');   /* { dg-warning "output may be truncated" } */
+  T (3, "%lc%lc%c", (wint_t)'1', (wint_t)'2', '3'); /* { dg-warning "output may be truncated" } */
 }
 
 #undef T
@@ -1530,7 +1555,7 @@ void test_snprintf_chk_c_const (void)
   /* Verify that specifying a size of the destination buffer that's
      bigger than its actual size (normally determined and passed to
      the function by __builtin_object_size) is diagnosed.  */
-  __builtin___snprintf_chk (buffer, 3, 0, 2, " ");   /* { dg-warning "specified bound 3 exceeds the size 2 of the destination" } */
+  FUNC (__snprintf_chk)(buffer, 3, 0, 2, " ");   /* { dg-warning "specified bound 3 exceeds the size 2 of the destination" } */
 
   T (-1, "%c",    0);           /* { dg-warning "specified bound \[0-9\]+ exceeds maximum object size \[0-9\]+" } */
 
@@ -1562,11 +1587,11 @@ void test_snprintf_chk_c_const (void)
   T (2, "%2lc", (wint_t)'1');          /* { dg-warning "output truncated before the last format character" } */
 
   T (3, "%lc%c",   (wint_t)'1', '2');
-  /* Here in the best case each argument will format as single character,
-     causing the output to be truncated just before the terminating NUL
-     (i.e., cutting off the '3').  */
-  T (3, "%lc%c%c", (wint_t)'1', '2', '3');   /* { dg-warning "output truncated" } */
-  T (3, "%lc%lc%c", (wint_t)'1', (wint_t)'2', '3'); /* { dg-warning "output truncated" } */
+  /* Here %lc may result in anywhere between 0 and MB_CUR_MAX characters
+     so the minimum number of bytes on output is 2 (plus the terminating
+     nul), but the likely number is 3 (plus the nul).  */
+  T (3, "%lc%c%c", (wint_t)'1', '2', '3');   /* { dg-warning "output may be truncated" } */
+  T (3, "%lc%lc%c", (wint_t)'1', (wint_t)'2', '3'); /* { dg-warning "output may be truncated" } */
 }
 
 /* Macro to verify that calls to __builtin_vsprintf (i.e., with no size
@@ -1581,15 +1606,15 @@ void test_vsprintf_s (va_list va)
 {
   T (-1, "%s");
 
-  T (0, "%s");              /* { dg-warning "writing a terminating nul past the end" } */
-  T (1, "%s");
+  T (0, "%s");              /* { dg-warning "writing likely 1 or more bytes" } */
+  T (1, "%s");              /* { dg-warning "may write a terminating nul" } */
   T (1, "%1s");             /* { dg-warning "writing a terminating nul past the end" } */
 
-  T (2, "%s%s");
-  T (2, "%s%s_");
-  T (2, "%s_%s");
-  T (2, "_%s%s");
-  T (2, "_%s_%s");          /* { dg-warning "writing a terminating nul past the end" } */
+  T (2, "%s%s");            /* { dg-warning "may write a terminating nul" } */
+  T (2, "%s%s_");           /* { dg-warning "writing 1 byte into a region of size between 0 and 2" } */
+  T (2, "%s_%s");           /* { dg-warning "writing likely 1 or more bytes into a region of size between 0 and 1" } */
+  T (2, "_%s%s");           /* { dg-warning "writing likely 1 or more bytes into a region of size between 0 and 1" } */
+  T (2, "_%s_%s");          /* { dg-warning "writing 1 byte into a region of size between 0 and" } */
 }
 
 /* Exercise all integer specifiers with no modifier and a non-constant
@@ -1648,14 +1673,14 @@ void test_vsnprintf_s (va_list va)
   T (-1, "%s");             /* { dg-warning "specified bound \[0-9\]+ exceeds maximum object size \[0-9\]+" } */
 
   T (0, "%s");
-  T (1, "%s");
+  T (1, "%s");              /* { dg-warning "output may be truncated" } */
   T (1, "%1s");             /* { dg-warning "output truncated before the last format character" } */
 
-  T (2, "%s%s");
-  T (2, "%s%s_");
-  T (2, "%s_%s");
-  T (2, "_%s%s");
-  T (2, "_%s_%s");          /* { dg-warning "output truncated before the last format character" } */
+  T (2, "%s%s");            /* { dg-warning "output may be truncated" } */
+  T (2, "%s%s_");           /* { dg-warning "output may be truncated writing 1 byte into a region of size between 0 and 2" } */
+  T (2, "%s_%s");           /* { dg-warning "directive output may be truncated writing likely 1 or more bytes into a region of size between 0 and 1" } */
+  T (2, "_%s%s");           /* { dg-warning "output may be truncated writing likely 1 or more bytes into a region of size between 0 and 1" } */
+  T (2, "_%s_%s");          /* { dg-warning "output may be truncated writing 1 byte into a region of size between 0 and 1" } */
 }
 
 #undef T
@@ -1669,17 +1694,17 @@ void test_vsnprintf_chk_s (va_list va)
   /* Verify that specifying a size of the destination buffer that's
      bigger than its actual size (normally determined and passed to
      the function by __builtin_object_size) is diagnosed.  */
-  __builtin___vsnprintf_chk (buffer, 123, 0, 122, "%-s", va);   /* { dg-warning "specified bound 123 exceeds the size 122 of the destination" } */
+  FUNC (__vsnprintf_chk)(buffer, 123, 0, 122, "%-s", va);   /* { dg-warning "specified bound 123 exceeds the size 122 of the destination" } */
 
-  __builtin___vsnprintf_chk (buffer, __SIZE_MAX__, 0, 2, "%-s", va);   /* { dg-warning "specified bound \[0-9\]+ exceeds maximum object size \[0-9\]+" } */
+  FUNC (__vsnprintf_chk)(buffer, __SIZE_MAX__, 0, 2, "%-s", va);   /* { dg-warning "specified bound \[0-9\]+ exceeds maximum object size \[0-9\]+" } */
 
   T (0, "%s");
-  T (1, "%s");
+  T (1, "%s");              /* { dg-warning "output may be truncated before the last format character" } */
   T (1, "%1s");             /* { dg-warning "output truncated before the last format character" } */
 
-  T (2, "%s%s");
-  T (2, "%s%s_");
-  T (2, "%s_%s");
-  T (2, "_%s%s");
-  T (2, "_%s_%s");          /* { dg-warning "output truncated before the last format character" } */
+  T (2, "%s%s");            /* { dg-warning "output may be truncated before the last format character" } */
+  T (2, "%s%s_");           /* { dg-warning "output may be truncated writing 1 byte into a region of size between 0 and 2" } */
+  T (2, "%s_%s");           /* { dg-warning "output may be truncated writing likely 1 or more bytes into a region of size between 0 and 1" } */
+  T (2, "_%s%s");           /* { dg-warning "output may be truncated writing likely 1 or more bytes into a region of size between 0 and 1" } */
+  T (2, "_%s_%s");          /* { dg-warning "output may be truncated writing 1 byte into a region of size between 0 and 1" } */
 }
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-2.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-2.c
index 7acb83d..75ee792 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-2.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-2.c
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-std=c99 -Wformat -Wformat-length=2 -ftrack-macro-expansion=0" } */
+/* { dg-options "-Wformat -Wformat-length=2 -ftrack-macro-expansion=0" } */
 
 /* When debugging, define LINE to the line number of the test case to exercise
    and avoid exercising any of the others.  The buffer and objsize macros
@@ -47,12 +47,16 @@ void test_s_const (void)
   T (1, "%*ls",  0, L"\0");
   T (1, "%*ls",  1, L"");       /* { dg-warning "nul past the end" } */
 
-  T (1, "%ls",      L"1");      /* { dg-warning "directive writing between 1 and 6 bytes into a region of size 1" } */
+  /* A wide character converts into between zero and MB_LEN_MAX bytes
+     (although individual ASCII characters are assumed to convert into
+     1 bt %lc so this could be made smarter.  */
+  T (1, "%ls",      L"1");      /* { dg-warning "directive writing up to 6 bytes into a region of size 1" } */
   T (1, "%.0ls",    L"1");
   T (2, "%.0ls",    L"1");
   T (2, "%.1ls",    L"1");
   T (2, "%.2ls",    L"1");      /* { dg-warning "nul past the end" } */
-  T (2, "%.3ls",    L"1");      /* { dg-warning "directive writing between 1 and 3 bytes into a region of size 2" } */
+  T (2, "%.3ls",    L"1");      /* { dg-warning "directive writing up to 3 bytes into a region of size 2" } */
+  T (2, "%.7ls",    L"1");      /* { dg-warning "directive writing up to 6 bytes into a region of size 2" } */
   T (2, "%.2ls",    L"12");     /* { dg-warning "nul past the end" } */
 
   /* The "%.2ls" directive below will write at a minimum 1 byte (because
@@ -71,8 +75,8 @@ void test_s_const (void)
   T (3, "%.3ls",    L"12");     /* { dg-warning "nul past the end" } */
   T (4, "%.3ls",    L"123");
   T (4, "%.4ls",    L"123");    /* { dg-warning "nul past the end" } */
-  T (4, "%.5ls",    L"123");    /* { dg-warning "directive writing between 3 and 5 bytes into a region of size 4" } */
-  T (4, "%.6ls",    L"123");    /* { dg-warning "directive writing between 3 and 6 bytes into a region of size 4" } */
+  T (4, "%.5ls",    L"123");    /* { dg-warning "directive writing up to 5 bytes into a region of size 4" } */
+  T (4, "%.6ls",    L"123");    /* { dg-warning "directive writing up to 6 bytes into a region of size 4" } */
 }
 
 
@@ -87,21 +91,32 @@ struct Arrays {
 
 /* Exercise buffer overflow detection with non-const string arguments.  */
 
-void test_s_nonconst (const char *s, const wchar_t *ws, struct Arrays *a)
+void test_s_nonconst (int w, int p, const char *s, const wchar_t *ws,
+		      struct Arrays *a)
 {
   T (0, "%s",   s);             /* { dg-warning "into a region" "sprintf transformed into strcpy" { xfail *-*-* } } */
   T (1, "%s",   s);             /* { dg-warning "nul past the end" "sprintf transformed into strcpy" { xfail *-*-* } } */
-  T (1, "%1s",  s);             /* { dg-warning "nul past the end" } */
+  T (1, "%1s",  s);             /* { dg-warning "writing a terminating nul" } */
   T (1, "%.0s", s);
   T (1, "%.1s", s);             /* { dg-warning "may write a terminating nul" } */
+  T (1, "%*s", 0, s);           /* { dg-warning "may write a terminating nul" } */
+  T (1, "%*s", 1, s);           /* { dg-warning "writing a terminating nul" } */
+  T (1, "%*s", 2, s);           /* { dg-warning "directive writing 2 or more bytes" } */
+  T (1, "%*s", 3, s);           /* { dg-warning "directive writing 3 or more bytes" } */
+
+  T (1, "%.*s", 1, s);          /* { dg-warning "may write a terminating nul" } */
+  T (1, "%.*s", 2, s);          /* { dg-warning "writing up to 2 bytes" } */
+  T (1, "%.*s", 3, s);          /* { dg-warning "writing up to 3 bytes" } */
 
   T (1, "%.0ls",  ws);
   T (1, "%.1ls",  ws);          /* { dg-warning "may write a terminating nul" } */
-  T (1, "%ls",    ws);          /* { dg-warning "writing a terminating nul" } */
+  T (1, "%ls",    ws);          /* { dg-warning "may write a terminating nul" } */
 
   /* Verify that the size of the array is used in lieu of its length.
-     The minus sign disables GCC's sprintf to strcpy transformation.  */
-  T (1, "%-s", a->a1);          /* { dg-warning "nul past the end" } */
+     The minus sign disables GCC's sprintf to strcpy transformation.
+     In the case below, the length of s->a1 can be at most zero, so
+     the call should not be diagnosed.  */
+  T (1, "%-s", a->a1);
 
   /* In the following test, since the length of the strings isn't known,
      their type (the array) is used to bound the maximum length to 1,
@@ -116,8 +131,8 @@ void test_s_nonconst (const char *s, const wchar_t *ws, struct Arrays *a)
 
   /* The length of a zero length array and flexible array member is
      unknown and at leve 2 assumed to be at least 1.  */
-  T (1, "%-s", a->a0);          /* { dg-warning "nul past the end" } */
-  T (1, "%-s", a->ax);          /* { dg-warning "nul past the end" } */
+  T (1, "%-s", a->a0);          /* { dg-warning "may write a terminating nul" } */
+  T (1, "%-s", a->ax);          /* { dg-warning "may write a terminating nul" } */
 
   T (2, "%-s", a->a0);
   T (2, "%-s", a->ax);
@@ -145,20 +160,20 @@ void test_hh_nonconst (int w, int p, int x, unsigned y)
 
   /* Zero precision means that zero argument formats as no bytes unless
      length or flags make it otherwise.  */
-  T (1, "%.*hhi",    0, x);     /* { dg-warning "between 0 and 4 bytes" } */
-  T (2, "%.*hhi",    0, x);     /* { dg-warning "between 0 and 4 bytes" } */
-  T (3, "%.*hhi",    0, x);     /* { dg-warning "between 0 and 4 bytes" } */
+  T (1, "%.*hhi",    0, x);     /* { dg-warning "writing up to 4 bytes" } */
+  T (2, "%.*hhi",    0, x);     /* { dg-warning "writing up to 4 bytes" } */
+  T (3, "%.*hhi",    0, x);     /* { dg-warning "writing up to 4 bytes" } */
   T (4, "%.*hhi",    0, x);     /* { dg-warning "may write a terminating nul past the end of the destination" } */
 
-  T (1, "%.*hhi",    0, y);     /* { dg-warning "between 0 and 4 bytes" } */
-  T (2, "%.*hhi",    0, y);     /* { dg-warning "between 0 and 4 bytes" } */
-  T (3, "%.*hhi",    0, y);     /* { dg-warning "between 0 and 4 bytes" } */
+  T (1, "%.*hhi",    0, y);     /* { dg-warning "writing up to 4 bytes" } */
+  T (2, "%.*hhi",    0, y);     /* { dg-warning "writing up to 4 bytes" } */
+  T (3, "%.*hhi",    0, y);     /* { dg-warning "writing up to 4 bytes" } */
   T (4, "%.*hhi",    0, y);     /* { dg-warning "may write a terminating nul past the end of the destination" } */
 
-  T (1, "%#.*hhi",    0, y);    /* { dg-warning "between 0 and 4 bytes" } */
+  T (1, "%#.*hhi",    0, y);    /* { dg-warning "writing up to 4 bytes" } */
   /* { dg-warning ".#. flag used" "-Wformat" { target *-*-* } .-1 } */
   T (1, "%+.*hhi",    0, y);    /* { dg-warning "between 1 and 4 bytes" } */
-  T (1, "%-.*hhi",    0, y);    /* { dg-warning "between 0 and 4 bytes" } */
+  T (1, "%-.*hhi",    0, y);    /* { dg-warning "writing up to 4 bytes" } */
   T (1, "% .*hhi",    0, y);    /* { dg-warning "between 1 and 4 bytes" } */
 
   T (1, "%#.*hhi",    1, y);    /* { dg-warning "between 1 and 4 bytes" } */
@@ -167,18 +182,18 @@ void test_hh_nonconst (int w, int p, int x, unsigned y)
   T (1, "%-.*hhi",    1, y);    /* { dg-warning "between 1 and 4 bytes" } */
   T (1, "% .*hhi",    1, y);    /* { dg-warning "between 2 and 4 bytes" } */
 
-  T (1, "%#.*hhi",    p, y);    /* { dg-warning "writing 0 or more bytes" } */
+  T (1, "%#.*hhi",    p, y);    /* { dg-warning "writing up to \[0-9\]+ bytes" } */
   /* { dg-warning ".#. flag used" "-Wformat" { target *-*-* } .-1 } */
-  T (1, "%+.*hhi",    p, y);    /* { dg-warning "writing 1 or more bytes" } */
-  T (1, "%-.*hhi",    p, y);    /* { dg-warning "writing 0 or more bytes" } */
-  T (1, "% .*hhi",    p, y);    /* { dg-warning "writing 1 or more bytes" } */
+  T (1, "%+.*hhi",    p, y);    /* { dg-warning "writing 1 or more bytes|writing between 1 and \[0-9\]+ bytes" } */
+  T (1, "%-.*hhi",    p, y);    /* { dg-warning "writing up to \[0-9\]+ bytes" } */
+  T (1, "% .*hhi",    p, y);    /* { dg-warning "writing between 1 and \[0-9\]+ bytes|writing 1 or more bytes" } */
 
-  T (1, "%#.*hhu",    0, y);    /* { dg-warning "between 0 and 3 bytes" } */
+  T (1, "%#.*hhu",    0, y);    /* { dg-warning "writing up to 3 bytes" } */
   /* { dg-warning ".#. flag used" "-Wformat" { target *-*-* } .-1 } */
-  T (1, "%+.*hhu",    0, y);    /* { dg-warning "between 0 and 3 bytes" } */
+  T (1, "%+.*hhu",    0, y);    /* { dg-warning "writing up to 3 bytes" } */
   /* { dg-warning ".\\+. flag used" "-Wformat" { target *-*-* } .-1 } */
-  T (1, "%-.*hhu",    0, y);    /* { dg-warning "between 0 and 3 bytes" } */
-  T (1, "% .*hhu",    0, y);    /* { dg-warning "between 0 and 3 bytes" } */
+  T (1, "%-.*hhu",    0, y);    /* { dg-warning "writing up to 3 bytes" } */
+  T (1, "% .*hhu",    0, y);    /* { dg-warning "writing up to 3 bytes" } */
   /* { dg-warning ". . flag used" "-Wformat" { target *-*-* } .-1 } */
 }
 
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c
index 00a3c7d..afa9aad 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c
@@ -47,7 +47,7 @@ void test_sprintf_chk_string (const char *s, const char *t)
   T (1, "%s", x ? "1" : "");       /* { dg-warning "nul past the end" } */
   T (1, "%s", x ? s : "1");        /* { dg-warning "nul past the end" } */
   T (1, "%s", x ? "1" : s);        /* { dg-warning "nul past the end" } */
-  T (1, "%s", x ? s : t);
+  T (1, "%s", x ? s : t);          /* { dg-warning "may write a terminating nul" } */
 
   T (2, "%s", x ? "" : "1");
   T (2, "%s", x ? "" : s);
@@ -198,10 +198,10 @@ void test_sprintf_chk_range_schar (void)
   T ( 3, "%i",  R (  0,  99));
   T ( 3, "%i",  R (  0, 100));  /* { dg-warning "may write a terminating nul past the end of the destination" } */
 
-  /* The following call may write as few as 3 bytes and as many as 5.
+  /* The following call may write as few as 2 bytes and as many as 4.
      It's a judgment call how best to diagnose it to make the potential
      problem clear.  */
-  T ( 3, "%i%i", R (1, 10), R (9, 10));   /* { dg-warning "may write a terminating nul past the end|.%i. directive writing between 1 and 2 bytes into a region of size 1" } */
+  T ( 3, "%i%i", R (1, 10), R (9, 10));   /* { dg-warning "directive writing between 1 and 2 bytes into a region of size between 1 and 2" } */
 
   T ( 4, "%i%i", R (10, 11), R (12, 13));   /* { dg-warning "nul past the end" } */
 
@@ -211,7 +211,11 @@ void test_sprintf_chk_range_schar (void)
   T ( 6, "%i_%i_%i", R (0, 9), R (0, 9), R (0, 10));  /* { dg-warning "may write a terminating nul past the end" } */
   T ( 6, "%i_%i_%i", R (0, 9), R (0, 10), R (0, 9));  /* { dg-warning "may write a terminating nul past the end" } */
   T ( 6, "%i_%i_%i", R (0, 10), R (0, 9), R (0, 9));  /* { dg-warning "may write a terminating nul past the end" } */
-  T ( 6, "%i_%i_%i", R (0, 9), R (0, 10), R (0, 10)); /* { dg-warning "may write a terminating nul past the end|.%i. directive writing between 1 and 2 bytes into a region of size 1" } */
+  T ( 6, "%hhi_%hi_%i", R (0, 9), R (0, 10), R (0, 10)); /* { dg-warning ".i. directive writing between 1 and 2 bytes into a region of size between 1 and 2" } */
+  T ( 6, "%3i|%2i/%1i", R (0, 99), R (0, 99), R (0, 99)); /* { dg-warning "./. directive writing 1 byte into a region of size 0" } */
+  T ( 6, "%.3i|%.2i/%i", R (0, 99), R (0, 99), R (0, 99)); /* { dg-warning "./. directive writing 1 byte into a region of size 0" } */
+  T ( 6, "%.3i|%.2i/%i", R (0, 119), R (0, 99), R (0, 99)); /* { dg-warning "./. directive writing 1 byte into a region of size 0" } */
+  T ( 6, "%.3i|%.2i/%i", R (0, 1), R (0, 2), R (0, 3)); /* { dg-warning "./. directive writing 1 byte into a region of size 0" } */
 }
 
 void test_sprintf_chk_range_uchar (void)
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c
index fd0a90a..66fc0a7 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c
@@ -19,7 +19,7 @@ void test (void)
      { dg-end-multiline-output "" }
      { dg-begin-multiline-output "-Wformat-length output" }
    sprintf (dst + 2, "1", 0);
-                      ~^
+                       ^
      { dg-end-multiline-output "" }
      { dg-begin-multiline-output "note" }
    sprintf (dst + 2, "1", 0);
@@ -70,7 +70,7 @@ void test (void)
      { dg-message ".sprintf. output 2 bytes into a destination of size 1" "note" { target *-*-* } .-2 }
      { dg-begin-multiline-output "-Wformat-length output" }
    sprintf (dst + 2, "%-s", "1");
-                      ~~~^
+                         ^
      { dg-end-multiline-output "" }
      { dg-begin-multiline-output "note" }
    sprintf (dst + 2, "%-s", "1");
@@ -160,7 +160,7 @@ void test_sprintf_note (void)
   /* { dg-warning "41: writing a terminating nul past the end of the destination" "" { target *-*-* } .-1 }
      { dg-begin-multiline-output "" }
    __builtin_sprintf (buffer (6), "%c%s%i", '1', "2", 3456);
-                                   ~~~~~~^
+                                         ^
      { dg-end-multiline-output "" }
 
      { dg-message ".__builtin_sprintf. output 7 bytes into a destination of size 6" "" { target *-*-* } .-7 }
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-7.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-7.c
index 0069348..0649038 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-7.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-7.c
@@ -47,7 +47,10 @@ void test_floating_a_cst (void)
   T (0, "%*a",  INT_MIN, 0.);     /* { dg-warning "writing 2147483648 bytes" } */
   T (0, "%*a",  INT_MAX, 0.);     /* { dg-warning "writing 2147483647 bytes" } */
 
-  T (0, "%.*a", INT_MIN, 0.);     /* { dg-warning "writing 6 bytes" } */
+  /* %a is poorly specified and as a result some implementations trim
+     redundant trailing zeros (e.g., Glibc) and others don't (e.g.,
+     Solaris).  */
+  T (0, "%.*a", INT_MIN, 0.);     /* { dg-warning "writing between 6 and 20 bytes" } */
 
   T (0, "%.*a", INT_MAX, 0.);     /* { dg-warning "writing 2147483654 bytes" } */
 
@@ -75,7 +78,7 @@ void test_floating_e_cst (void)
   T (0, "%*e",  INT_MIN, 0.);     /* { dg-warning "writing 2147483648 bytes" } */
   T (0, "%*e",  INT_MAX, 0.);     /* { dg-warning "writing 2147483647 bytes" } */
 
-  T (0, "%.*e", INT_MIN, 0.);     /* { dg-warning "writing 5 bytes" } */
+  T (0, "%.*e", INT_MIN, 0.);     /* { dg-warning "writing 12 bytes" } */
 
   T (0, "%.*e", INT_MAX, 0.);     /* { dg-warning "writing 2147483653 bytes" } */
 
@@ -103,7 +106,7 @@ void test_floating_f_cst (void)
   T (0, "%*f",  INT_MIN, 0.);     /* { dg-warning "writing 2147483648 bytes" } */
   T (0, "%*f",  INT_MAX, 0.);     /* { dg-warning "writing 2147483647 bytes" } */
 
-  T (0, "%.*f", INT_MIN, 0.);     /* { dg-warning "writing 1 byte" } */
+  T (0, "%.*f", INT_MIN, 0.);     /* { dg-warning "writing 8 bytes" } */
 
   T (0, "%.*f", INT_MAX, 0.);     /* { dg-warning "writing 2147483649 bytes" } */
 
@@ -170,14 +173,14 @@ void test_string_cst (void)
 
 void test_string_var (const char *s)
 {
-  T (0, "%*s",  INT_MIN, s);     /* { dg-warning "writing 2147483648 bytes" } */
-  T (0, "%*s",  INT_MAX, s);     /* { dg-warning "writing 2147483647 bytes" } */
+  T (0, "%*s",  INT_MIN, s);     /* { dg-warning "writing 2147483648 or more bytes" } */
+  T (0, "%*s",  INT_MAX, s);     /* { dg-warning "writing 2147483647 or more bytes" } */
 
-  T (0, "%.*s", INT_MIN, s);     /* { dg-warning "writing a terminating nul" } */
+  T (0, "%.*s", INT_MIN, s);     /* { dg-warning "writing likely 1 or more bytes" } */
 
-  T (0, "%.*s", INT_MAX, s);     /* { dg-warning "writing between 0 and 2147483647 bytes" } */
+  T (0, "%.*s", INT_MAX, s);     /* { dg-warning "writing up to 2147483647 bytes" } */
 
-  T (0, "%*.*s", INT_MIN, INT_MIN, s);   /* { dg-warning "writing 2147483648 bytes" } */
+  T (0, "%*.*s", INT_MIN, INT_MIN, s);   /* { dg-warning "writing 2147483648 or more bytes" } */
 
   T (0, "%*.*s", INT_MAX, INT_MAX, s);   /* { dg-warning "writing 2147483647 bytes" } */
 }
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-9.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-9.c
new file mode 100644
index 0000000..0cf92c3
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-9.c
@@ -0,0 +1,158 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -Wformat -Wformat-length=2 -ftrack-macro-expansion=0" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+#define INT_MAX   __INT_MAX__
+#define INT_MIN   (-INT_MAX - 1)
+
+#ifndef LINE
+#  define LINE 0
+#endif
+
+int dummy_sprintf (char*, const char*, ...);
+void sink (void*);
+
+char buffer[4096];
+char *ptr;
+
+/* Helper to expand function to either __builtin_f or dummy_f to
+   make debugging GCC easy.  */
+#define FUNC(f)							\
+  ((!LINE || LINE == __LINE__) ? __builtin_ ## f : dummy_ ## f)
+
+/* Evaluate to an array of SIZE characters when non-negative, or to
+   a pointer to an unknown object otherwise.  */
+#define buffer(size)					\
+  ((0 <= size) ? buffer + sizeof buffer - (size) : ptr)
+
+#define T(bufsize, fmt, ...)						\
+  do {									\
+    char *buf = buffer (bufsize);					\
+    FUNC (sprintf)(buf, fmt, __VA_ARGS__);				\
+    sink (buf);								\
+  } while (0)
+
+
+/* Identity function to verify that the checker figures out the value
+   of the operand even when it's not constant (i.e., makes use of
+   inlining and constant propagation information).  */
+
+int i (int x) { return x; }
+const char* s (const char *str) { return str; }
+
+/* Function to "generate" a unique unknown number (as far as GCC can
+   tell) each time it's called.  It prevents the optimizer from being
+   able to narrow down the ranges of possible values in test functions
+   with repeated references to the same variable.  */
+extern int value (void);
+
+/* Return a value in the range [MIN, MAX].  */
+int range (int min, int max)
+{
+  int val = value ();
+  return val < min || max < val ? min : val;
+}
+
+#define R(min, max) range (min, max)
+
+/* Verify that the checker can detect buffer overflow when the "%s"
+   argument is in a known range of lengths and one or both of which
+   exceed the size of the destination.  */
+
+void test_sprintf_chk_string (const char *s)
+{
+  T (1, "%*s", R (0, 1), "");     /* { dg-warning "may write a terminating nul" } */
+  T (1, "%*s", R (-2, -1), "");   /* { dg-warning "writing up to 2 bytes" } */
+  T (1, "%*s", R (-3,  2), "");   /* { dg-warning "writing up to 3 bytes" } */
+  T (1, "%*s", R (-4,  5), "");   /* { dg-warning "writing up to 5 bytes" } */
+
+  T (1, "%*s", R ( -5, 6), "1");  /* { dg-warning "writing between 1 and 6 bytes" } */
+  T (1, "%*s", R ( -6, 7), "12"); /* { dg-warning "writing between 2 and 7 bytes" } */
+
+  T (1, "%.*s", R (0, 1), "");
+  T (1, "%.*s", R (0, 1), s);     /* { dg-warning "may write a terminating nul" } */
+  T (1, "%.*s", R (-2, -1), "");
+  T (1, "%.*s", R (-2, -1), s);   /* { dg-warning "may write a terminating nul" } */
+  T (1, "%.*s", R (-3,  2), "");
+  T (1, "%.*s", R (-4,  5), "");
+
+  T (1, "%.*s", R ( -5, 6), "1");  /* { dg-warning "may write a terminating nul" } */
+  T (1, "%.*s", R ( -6, 7), "12"); /* { dg-warning "writing up to 2 bytes " } */
+  T (1, "%.*s", R (  1, 7), "12"); /* { dg-warning "writing between 1 and 2 bytes " } */
+  T (1, "%.*s", R (  2, 7), "12"); /* { dg-warning "writing 2 bytes " } */
+
+  T (1, "%*.*s", R (0, 1), R (0, 1), "");     /* { dg-warning "may write a terminating nul" } */
+  T (1, "%*.*s", R (0, 2), R (0, 1), "");     /* { dg-warning "directive writing up to 2 bytes into a region of size 1" } */
+  T (1, "%*.*s", R (0, 3), R (0, 1), "");     /* { dg-warning "writing up to 3 bytes" } */
+
+  T (1, "%*.*s", R (0, 1), R (0, 1), "1");    /* { dg-warning "may write a terminating nul" } */
+  T (1, "%*.*s", R (0, 2), R (0, 1), "1");    /* { dg-warning "writing up to 2 bytes" } */
+  T (1, "%*.*s", R (0, 3), R (0, 1), "1");    /* { dg-warning "writing up to 3 bytes" } */
+
+  T (1, "%*.*s", R (0, 1), R (0, 1), "12");   /* { dg-warning "may write a terminating nul" } */
+  T (1, "%*.*s", R (0, 2), R (0, 1), "12");   /* { dg-warning "writing up to 2 bytes" } */
+  T (1, "%*.*s", R (0, 3), R (0, 1), "12");   /* { dg-warning "writing up to 3 bytes" } */
+
+  T (1, "%*.*s", R (0, 1), R (0, 1), "123");  /* { dg-warning "may write a terminating nul" } */
+  T (1, "%*.*s", R (0, 2), R (0, 1), "123");  /* { dg-warning "writing up to 2 bytes" } */
+  T (1, "%*.*s", R (0, 3), R (0, 1), "123");  /* { dg-warning "writing up to 3 bytes" } */
+  T (1, "%*.*s", R (0, 3), R (0, 1), s);      /* { dg-warning "writing up to 3 bytes" } */
+
+  T (1, "%*.*s", R (0, 1), R (0, 2), "123");  /* { dg-warning "writing up to 2 bytes" } */
+  T (1, "%*.*s", R (0, 2), R (0, 2), "123");  /* { dg-warning "writing up to 2 bytes" } */
+  T (1, "%*.*s", R (0, 3), R (0, 2), "123");  /* { dg-warning "writing up to 3 bytes" } */
+  T (1, "%*.*s", R (0, 3), R (0, 2), s);      /* { dg-warning "writing up to 3 bytes" } */
+
+  T (1, "%*.*s", R (0, 1), R (0, 3), "123");  /* { dg-warning "writing up to 3 bytes" } */
+  T (1, "%*.*s", R (0, 2), R (0, 3), "123");  /* { dg-warning "writing up to 3 bytes" } */
+  T (1, "%*.*s", R (0, 3), R (0, 3), "123");  /* { dg-warning "writing up to 3 bytes" } */
+  T (1, "%*.*s", R (0, 3), R (0, 3), s);      /* { dg-warning "writing up to 3 bytes" } */
+
+  T (1, "%*.*s", R (1, 1), R (0, 3), "123");  /* { dg-warning "writing between 1 and 3 bytes" } */
+  T (1, "%*.*s", R (1, 2), R (0, 3), "123");  /* { dg-warning "writing between 1 and 3 bytes" } */
+  T (1, "%*.*s", R (1, 3), R (0, 3), "123");  /* { dg-warning "writing between 1 and 3 bytes" } */
+  T (1, "%*.*s", R (1, 3), R (0, 3), s);      /* { dg-warning "writing between 1 and 3 bytes" } */
+
+  T (1, "%*.*s", R (1, 1), R (1, 3), "123");  /* { dg-warning "writing between 1 and 3 bytes" } */
+  T (1, "%*.*s", R (1, 2), R (1, 3), "123");  /* { dg-warning "writing between 1 and 3 bytes" } */
+  T (1, "%*.*s", R (1, 3), R (1, 3), "123");  /* { dg-warning "writing between 1 and 3 bytes" } */
+  T (1, "%*.*s", R (1, 3), R (1, 3), s);      /* { dg-warning "writing between 1 and 3 bytes" } */
+
+  T (1, "%*.*s", R (2, 3), R (1, 3), "123");  /* { dg-warning "writing between 2 and 3 bytes" } */
+  T (1, "%*.*s", R (3, 4), R (1, 3), "123");  /* { dg-warning "writing between 3 and 4 bytes" } */
+  T (1, "%*.*s", R (4, 5), R (1, 3), "123");  /* { dg-warning "writing between 4 and 5 bytes" } */
+  T (1, "%*.*s", R (2, 3), R (1, 3), s);      /* { dg-warning "writing between 2 and 3 bytes" } */
+}
+
+void test_sprintf_chk_int (int w, int p, int i)
+{
+  T (1, "%*d", w, 0);             /* { dg-warning "may write a terminating nul|directive writing between 1 and \[0-9\]+ bytes" } */
+  T (1, "%*d", w, i);             /* { dg-warning "may write a terminating nul|directive writing between 1 and \[0-9\]+ bytes" } */
+
+  T (1, "%*d", R (-1, 1), 0);     /* { dg-warning "writing a terminating nul" } */
+  T (1, "%*d", R ( 0, 1), 0);     /* { dg-warning "writing a terminating nul" } */
+  T (1, "%+*d", R ( 0, 1), 0);    /* { dg-warning "directive writing 2 bytes" } */
+  T (1, "%+*u", R ( 0, 1), 0);    /* { dg-warning "writing a terminating nul" } */
+  T (2, "%*d", R (-3, -2), 0);     /* { dg-warning "directive writing between 1 and 3 bytes" } */
+  T (2, "%*d", R (-3, -1), 0);     /* { dg-warning "directive writing between 1 and 3 bytes" } */
+  T (2, "%*d", R (-3,  0), 0);     /* { dg-warning "directive writing between 1 and 3 bytes" } */
+  T (2, "%*d", R (-2, -1), 0);     /* { dg-warning "may write a terminating nul" } */
+  T (2, "%*d", R (-2,  2), 0);     /* { dg-warning "may write a terminating nul" } */
+  T (2, "%*d", R (-1,  2), 0);     /* { dg-warning "may write a terminating nul" } */
+  T (2, "%*d", R ( 0,  2), 0);     /* { dg-warning "may write a terminating nul" } */
+  T (2, "%*d", R ( 1,  2), 0);     /* { dg-warning "may write a terminating nul" } */
+
+  T (1, "%.*d", p, 0);             /* { dg-warning "may write a terminating nul|directive writing up to \[0-9\]+ bytes" } */
+  T (1, "%.*d", p, i);             /* { dg-warning "may write a terminating nul||directive writing up to \[0-9\]+ bytes" } */
+  T (1, "%.*d", R (INT_MIN, -1), 0);     /* { dg-warning "writing a terminating nul" } */
+  T (1, "%.*d", R (INT_MIN,  0), 0);     /* { dg-warning "may write a terminating nul" } */
+  T (1, "%.*d", R (-2, -1), 0);     /* { dg-warning "writing a terminating nul" } */
+  T (1, "%.*d", R (-1,  1), 0);     /* { dg-warning "may write a terminating nul" } */
+  T (1, "%.*d", R ( 0,  1), 0);     /* { dg-warning "may write a terminating nul" } */
+  T (1, "%.*d", R ( 0,  2), 0);     /* { dg-warning "directive writing up to 2 bytes" } */
+  T (1, "%.*d", R ( 0,  INT_MAX - 1), 0);     /* { dg-warning "directive writing up to \[0-9\]+ bytes" } */
+  T (1, "%.*d", R ( 1,  INT_MAX - 1), 0);     /* { dg-warning "directive writing between 1 and \[0-9\]+ bytes" } */
+}
+
+/* { dg-prune-output "flag used with .%.. gnu_printf format" } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf.c
index 35a5bd0..b559090 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf.c
@@ -411,14 +411,19 @@ test_a_double (double d)
   EQL ( 6,  7, "%.0a", 0.0);        /* 0x0p+0 */
   EQL ( 6,  7, "%.0a", 1.0);        /* 0x8p-3 */
   EQL ( 6,  7, "%.0a", 2.0);        /* 0x8p-2 */
-  EQL ( 8,  9, "%.1a", 3.0);        /* 0xc.0p-2 */
-  EQL ( 9, 10, "%.2a", 4.0);        /* 0x8.00p-1 */
-  EQL (10, 11, "%.3a", 5.0);        /* 0xa.000p-1 */
+
+  /* The decimal point may be up to MB_LEN_MAX long.  */
+  RNG ( 8, 13, 14, "%.1a", 3.0);    /* 0xc.0p-2 */
+  RNG ( 9, 14, 15, "%.2a", 4.0);    /* 0x8.00p-1 */
+  RNG (10, 15, 16, "%.3a", 5.0);    /* 0xa.000p-1 */
 
 	                            /* d is in [ 0, -DBL_MAX ] */
   RNG ( 6, 10, 11, "%.0a", d);      /* 0x0p+0 ... -0x2p+1023 */
-  RNG ( 6, 12, 13, "%.1a", d);      /* 0x0p+0 ... -0x2.0p+1023 */
-  RNG ( 6, 13, 14, "%.2a", d);      /* 0x0p+0 ... -0x2.00p+1023 */
+  /* %a is poorly specified and allows for implementations divergence:
+     some (such as Glibc) trim redundant trailing zeros after decimal
+     point and others (e.g., Solaris) don't.  */
+  RNG ( 6, 30, 31, "%.1a", d);      /* 0x0p+0 ... -0x2.0...0p+1023 */
+  RNG ( 6, 30, 31, "%.2a", d);      /* 0x0p+0 ... -0x2.00...0p+1023 */
 }
 
 static void __attribute__ ((noinline, noclone))
@@ -427,111 +432,113 @@ test_a_long_double (void)
   EQL ( 6,  7, "%.0La", 0.0L);      /* 0x0p+0 */
   EQL ( 6,  7, "%.0La", 1.0L);      /* 0x8p-3 */
   EQL ( 6,  7, "%.0La", 2.0L);      /* 0x8p-2 */
-  EQL ( 8,  9, "%.1La", 3.0L);      /* 0xc.0p-2 */
-  EQL ( 9, 10, "%.2La", 4.0L);      /* 0xa.00p-1 */
+
+  RNG ( 8, 13, 14, "%.1La", 3.0L);  /* 0xc.0p-2 */
+  RNG ( 9, 14, 15, "%.2La", 4.0L);  /* 0xa.00p-1 */
 }
 
 static void __attribute__ ((noinline, noclone))
 test_e_double (void)
 {
-  EQL (12, 13, "%e",  1.0e0);
-  EQL (13, 14, "%e", -1.0e0);
-  EQL (12, 13, "%e",  1.0e+1);
-  EQL (13, 14, "%e", -1.0e+1);
-  EQL (12, 13, "%e",  1.0e+12);
-  EQL (13, 14, "%e", -1.0e+12);
-  EQL (13, 14, "%e",  1.0e+123);
-  EQL (14, 15, "%e", -1.0e+123);
-
-  EQL (12, 13, "%e",  9.999e+99);
-  EQL (12, 13, "%e",  9.9999e+99);
-  EQL (12, 13, "%e",  9.99999e+99);
+  RNG (12, 17, 18, "%e",  1.0e0);
+  RNG (13, 18, 19, "%e", -1.0e0);
+  RNG (12, 17, 18, "%e",  1.0e+1);
+  RNG (13, 18, 19, "%e", -1.0e+1);
+  RNG (12, 17, 18, "%e",  1.0e+12);
+  RNG (13, 18, 19, "%e", -1.0e+12);
+  RNG (13, 18, 19, "%e",  1.0e+123);
+  RNG (14, 19, 20, "%e", -1.0e+123);
+
+  RNG (12, 17, 18, "%e",  9.999e+99);
+  RNG (12, 17, 18, "%e",  9.9999e+99);
+  RNG (12, 17, 18, "%e",  9.99999e+99);
 
   /* The actual output of the following directive depends on the rounding
      mode.  */
-  /* EQL (12, "%e",  9.9999994e+99); */
+  /* RNG (12, "%e",  9.9999994e+99); */
 
-  EQL (12, 13, "%e",  1.0e-1);
-  EQL (12, 13, "%e",  1.0e-12);
-  EQL (13, 14, "%e",  1.0e-123);
+  RNG (12, 17, 18, "%e",  1.0e-1);
+  RNG (12, 17, 18, "%e",  1.0e-12);
+  RNG (13, 18, 19, "%e",  1.0e-123);
 }
 
 static void __attribute__ ((noinline, noclone))
 test_e_long_double (void)
 {
-  EQL (12, 13, "%Le",  1.0e0L);
-  EQL (13, 14, "%Le", -1.0e0L);
-  EQL (12, 13, "%Le",  1.0e+1L);
-  EQL (13, 14, "%Le", -1.0e+1L);
-  EQL (12, 13, "%Le",  1.0e+12L);
-  EQL (13, 14, "%Le", -1.0e+12L);
-  EQL (13, 14, "%Le",  1.0e+123L);
-  EQL (14, 15, "%Le", -1.0e+123L);
-
-  EQL (12, 13, "%Le",  9.999e+99L);
-  EQL (12, 13, "%Le",  9.9999e+99L);
-  EQL (12, 13, "%Le",  9.99999e+99L);
+  RNG (12, 17, 18, "%Le",  1.0e0L);
+  RNG (13, 18, 19, "%Le", -1.0e0L);
+  RNG (12, 17, 18, "%Le",  1.0e+1L);
+  RNG (13, 18, 19, "%Le", -1.0e+1L);
+  RNG (12, 18, 19, "%Le",  1.0e+12L);
+  RNG (13, 19, 20, "%Le", -1.0e+12L);
+  RNG (13, 19, 20, "%Le",  1.0e+123L);
+  RNG (14, 20, 21, "%Le", -1.0e+123L);
+
+  RNG (12, 18, 19, "%Le",  9.999e+99L);
+  RNG (12, 18, 19, "%Le",  9.9999e+99L);
+  RNG (12, 18, 19, "%Le",  9.99999e+99L);
 
 #if __DBL_DIG__ < __LDBL_DIG__
-  EQL (12, 13, "%Le",  9.999999e+99L);
+  RNG (12, 17, 18, "%Le",  9.999999e+99L);
 #else
-  RNG (12, 13, 14, "%Le",  9.999999e+99L);
+  RNG (12, 18, 19, "%Le",  9.999999e+99L);
 #endif
 
   /* The actual output of the following directive depends on the rounding
      mode.  */
-  /* EQL (12, "%Le",  9.9999994e+99L); */
+  /* RNG (12, "%Le",  9.9999994e+99L); */
 
-  EQL (12, 13, "%Le",  1.0e-1L);
-  EQL (12, 13, "%Le",  1.0e-12L);
-  EQL (13, 14, "%Le",  1.0e-123L);
+  RNG (12, 17, 18, "%Le",  1.0e-1L);
+  RNG (12, 17, 18, "%Le",  1.0e-12L);
+  RNG (13, 18, 19, "%Le",  1.0e-123L);
 
   EQL ( 6,  7, "%.0Le",   1.0e-111L);
-  EQL ( 8,  9, "%.1Le",   1.0e-111L);
-  EQL (19, 20, "%.12Le",  1.0e-112L);
-  EQL (20, 21, "%.13Le",  1.0e-113L);
+
+  RNG ( 8, 13, 14, "%.1Le",   1.0e-111L);
+  RNG (19, 25, 25, "%.12Le",  1.0e-112L);
+  RNG (20, 26, 27, "%.13Le",  1.0e-113L);
 }
 
 static void __attribute__ ((noinline, noclone))
 test_f_double (void)
 {
-  EQL (  8,   9, "%f", 0.0e0);
-  EQL (  8,   9, "%f", 0.1e0);
-  EQL (  8,   9, "%f", 0.12e0);
-  EQL (  8,   9, "%f", 0.123e0);
-  EQL (  8,   9, "%f", 0.1234e0);
-  EQL (  8,   9, "%f", 0.12345e0);
-  EQL (  8,   9, "%f", 0.123456e0);
-  EQL (  8,   9, "%f", 1.234567e0);
-
-  EQL (  9,  10, "%f", 1.0e+1);
-  EQL ( 20,  21, "%f", 1.0e+12);
-  EQL (130, 131, "%f", 1.0e+123);
-
-  EQL (  8,   9, "%f", 1.0e-1);
-  EQL (  8,   9, "%f", 1.0e-12);
-  EQL (  8,   9, "%f", 1.0e-123);
+  RNG (  8,  13,  14, "%f", 0.0e0);
+  RNG (  8,  13,  14, "%f", 0.1e0);
+  RNG (  8,  13,  14, "%f", 0.12e0);
+  RNG (  8,  13,  14, "%f", 0.123e0);
+  RNG (  8,  13,  14, "%f", 0.1234e0);
+  RNG (  8,  13,  14, "%f", 0.12345e0);
+  RNG (  8,  13,  14, "%f", 0.123456e0);
+  RNG (  8,  13,  14, "%f", 1.234567e0);
+
+  RNG (  9,  14,  15, "%f", 1.0e+1);
+  RNG ( 20,  26,  27, "%f", 1.0e+12);
+  RNG (130, 136, 137, "%f", 1.0e+123);
+
+  RNG (  8,  13,  14, "%f", 1.0e-1);
+  RNG (  8,  13,  14, "%f", 1.0e-12);
+  RNG (  8,  13,  14, "%f", 1.0e-123);
 }
 
 static void __attribute__ ((noinline, noclone))
 test_f_long_double (void)
 {
-  EQL (  8,   9, "%Lf", 0.0e0L);
-  EQL (  8,   9, "%Lf", 0.1e0L);
-  EQL (  8,   9, "%Lf", 0.12e0L);
-  EQL (  8,   9, "%Lf", 0.123e0L);
-  EQL (  8,   9, "%Lf", 0.1234e0L);
-  EQL (  8,   9, "%Lf", 0.12345e0L);
-  EQL (  8,   9, "%Lf", 0.123456e0L);
-  EQL (  8,   9, "%Lf", 1.234567e0L);
-
-  EQL (  9,  10, "%Lf", 1.0e+1L);
-  EQL ( 20,  21, "%Lf", 1.0e+12L);
-  EQL (130, 131, "%Lf", 1.0e+123L);
-
-  EQL (  8,   9, "%Lf", 1.0e-1L);
-  EQL (  8,   9, "%Lf", 1.0e-12L);
-  EQL (  8,   9, "%Lf", 1.0e-123L);
+  RNG (  8,  15,  16, "%Lf", 0.0e0L);
+  RNG (  8,  14,  15, "%Lf", 0.1e0L);
+  RNG (  8,  14,  15, "%Lf", 0.12e0L);
+  RNG (  8,  14,  15, "%Lf", 0.123e0L);
+  RNG (  8,  14,  15, "%Lf", 0.1234e0L);
+  RNG (  8,  14,  15, "%Lf", 0.12345e0L);
+  RNG (  8,  14,  15, "%Lf", 0.123456e0L);
+  RNG (  8,  14,  15, "%Lf", 1.234567e0L);
+
+  RNG (  9,  15,  16, "%Lf", 1.0e+1L);
+  RNG ( 20,  26,  27, "%Lf", 1.0e+12L);
+  RNG (130, 136, 137, "%Lf", 1.0e+123L);
+
+  RNG (  8,  14,  15, "%Lf", 1.0e-1L);
+  RNG (  8,  14,  15, "%Lf", 1.0e-12L);
+  RNG (  8,  14,  15, "%Lf", 1.0e-123L);
 }
 
 static void __attribute__ ((noinline, noclone))
diff --git a/gcc/vec.h b/gcc/vec.h
index 3877f2d..02063d0 100644
--- a/gcc/vec.h
+++ b/gcc/vec.h
@@ -1619,7 +1619,6 @@ vec<T, va_heap, vl_ptr>::safe_grow_cleared (unsigned len MEM_STAT_DECL)
     }
 }
 
-
 /* Same as vec::safe_grow but without reallocation of the internal vector.
    If the vector cannot be extended, a runtime assertion will be triggered.  */
 

Patch hide | download patch | download mbox

diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c
index 22ef86b..0c804f4 100644
--- a/gcc/gimple-ssa-sprintf.c
+++ b/gcc/gimple-ssa-sprintf.c
@@ -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
    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
@@ -92,6 +92,10 @@  along with GCC; see the file COPYING3.  If not see
 
 namespace {
 
+static bool
+get_int_range (tree, tree, HOST_WIDE_INT *, HOST_WIDE_INT *,
+	       bool, HOST_WIDE_INT);
+
 const pass_data pass_data_sprintf_length = {
   GIMPLE_PASS,             // pass type
   "printf-return-value",   // pass name
@@ -145,50 +149,53 @@  pass_sprintf_length::gate (function *)
 	  && (optimize > 0) == fold_return_value);
 }
 
+/* The minimum, maximum, likely, and unlikely maximum number of bytes
+   of output either a formatting function or an individual directive
+   can result in.  */
+
+struct result_range
+{
+  /* The absolute minimum number of bytes.  The result of a successful
+     conversion is guaranteed to be no less than this.  (An erroneous
+     conversion can be indicated by MIN > HOST_WIDE_INT_MAX.)  */
+  unsigned HOST_WIDE_INT min;
+  /* The likely maximum result that is used in diagnostics.  In most
+     cases MAX is the same as the worst case UNLIKELY result.  */
+  unsigned HOST_WIDE_INT max;
+  /* The likely result used to trigger diagnostics.  For conversions
+     that result in a range of bytes [MIN, MAX], LIKELY is somewhere
+     in that range.  */
+  unsigned HOST_WIDE_INT likely;
+  /* In rare cases (e.g., for nultibyte characters) UNLIKELY gives
+     the worst cases maximum result of a directive.  In most cases
+     UNLIKELY == MAX.  UNLIKELY is used to control the return value
+     optimization but not in diagnostics.  */
+  unsigned HOST_WIDE_INT unlikely;
+};
+
 /* The result of a call to a formatted function.  */
 
 struct format_result
 {
-  /* Number of characters written by the formatted function, exact,
-     minimum and maximum when an exact number cannot be determined.
-     Setting the minimum to HOST_WIDE_INT_MAX disables all length
-     tracking for the remainder of the format string.
-     Setting either of the other two members to HOST_WIDE_INT_MAX
-     disables the exact or maximum length tracking, respectively,
-     but continues to track the maximum.  */
-  unsigned HOST_WIDE_INT number_chars;
-  unsigned HOST_WIDE_INT number_chars_min;
-  unsigned HOST_WIDE_INT number_chars_max;
-
-  /* True when the range given by NUMBER_CHARS_MIN and NUMBER_CHARS_MAX
-     can be relied on for value range propagation, false otherwise.
-     This means that BOUNDED must not be set if the number of bytes
-     produced by any directive is unspecified or implementation-
-     defined (unless the implementation's behavior is known and
-     determined via a target hook).
-     Note that BOUNDED only implies that the length of a function's
-     output is known to be within some range, not that it's constant
-     and a candidate for string folding.  BOUNDED is a stronger
-     guarantee than KNOWNRANGE.  */
-  bool bounded;
+  /* Range of characters written by the formatted function.
+     Setting the minimum to HOST_WIDE_INT_MAX disables all
+     length tracking for the remainder of the format string.  */
+  result_range range;
 
   /* True when the range above is obtained from known values of
-     directive arguments or their bounds and not the result of
-     heuristics that depend on warning levels.  It is used to
-     issue stricter diagnostics in cases where strings of unknown
-     lengths are bounded by the arrays they are determined to
-     refer to.  KNOWNRANGE must not be used to set the range of
-     the return value of a call.  */
+     directive arguments, or bounds on the amount of output such
+     as width and precision, and not the result of  heuristics that
+     depend on warning levels.  It's used to issue stricter diagnostics
+     in cases where strings of unknown lengths are bounded by the arrays
+     they are determined to refer to.  KNOWNRANGE must not be used for
+     the return value optimization.  */
   bool knownrange;
 
-  /* True when the output of the formatted call is constant (and
-     thus a candidate for string constant folding).  This is rare
-     and typically requires that the arguments of all directives
-     are also constant.  CONSTANT implies BOUNDED.  */
-  bool constant;
-
   /* True if no individual directive resulted in more than 4095 bytes
-     of output (the total NUMBER_CHARS might be greater).  */
+     of output (the total NUMBER_CHARS_{MIN,MAX} might be greater).
+     Implementations are not required to handle directives that produce
+     more than 4K bytes (leading to undefined behavior) and so when one
+     is found it disables the return value optimization.  */
   bool under4k;
 
   /* True when a floating point directive has been seen in the format
@@ -197,7 +204,7 @@  struct format_result
 
   /* True when an intermediate result has caused a warning.  Used to
      avoid issuing duplicate warnings while finishing the processing
-     of a call.  */
+     of a call.  WARNED also disables the return value optimization.  */
   bool warned;
 
   /* Preincrement the number of output characters by 1.  */
@@ -215,20 +222,29 @@  struct format_result
   }
 
   /* 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 (range.min < HOST_WIDE_INT_MAX)
+    range.min += n;
+
+  if (range.max < HOST_WIDE_INT_MAX)
+    range.max += n;
+
+  if (range.likely < HOST_WIDE_INT_MAX)
+    range.likely += n;
+
+  if (range.unlikely < HOST_WIDE_INT_MAX)
+    range.unlikely += n;
+
+  return *this;
+}
+
 /* Return the value of INT_MIN for the target.  */
 
 static inline HOST_WIDE_INT
@@ -426,25 +442,45 @@  enum format_lengths
 };
 
 
-/* A minimum and maximum number of bytes.  */
-
-struct result_range
-{
-  unsigned HOST_WIDE_INT min, max;
-};
-
 /* Description of the result of conversion either of a single directive
    or the whole format string.  */
 
 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),
+    nullp ()
+  {
+    range.min = min;
+    range.max = min;
+    range.likely = min;
+    range.unlikely = min;
+  }
+
+  /* Construct a FMTRESULT object with MIN, MAX, and LIKELY counters.
+     KNOWNRANGE is set when both MIN and MAX are valid.   */
+  fmtresult (unsigned HOST_WIDE_INT min, unsigned HOST_WIDE_INT max,
+	     unsigned HOST_WIDE_INT likely = HOST_WIDE_INT_MAX)
+  : argmin (), argmax (),
+    knownrange (min < HOST_WIDE_INT_MAX && max < HOST_WIDE_INT_MAX),
+    nullp ()
   {
-    range.min = range.max = HOST_WIDE_INT_MAX;
+    range.min = min;
+    range.max = max;
+    range.likely = max < likely ? min : likely;
+    range.unlikely = max;
   }
 
-  /* The range a directive's argument is in.  */
+/* Adjust result upward to reflect the RANGE of values the specified
+   width or precision is known to be in.  */
+  fmtresult& adjust_for_width_or_precision (const HOST_WIDE_INT[2],
+					    tree = NULL_TREE,
+					    unsigned = 0, unsigned = 0);
+
+ /* The range a directive's argument is in.  */
   tree argmin, argmax;
 
   /* The minimum and maximum number of bytes that a directive
@@ -456,26 +492,117 @@  struct fmtresult
      heuristics that depend on warning levels.  */
   bool knownrange;
 
-  /* True when the range is the result of an argument determined
-     to be bounded to a subrange of its type or value (such as by
-     value range propagation or the width of the formt directive),
-     false otherwise.  */
-  bool bounded;
-
-  /* True when the output of a directive is constant.  This is rare
-     and typically requires that the argument(s) of the directive
-     are also constant (such as determined by constant propagation,
-     though not value range propagation).  */
-  bool constant;
-
   /* True when the argument is a null pointer.  */
   bool nullp;
+
+  /* Return the maximum number of decimal digits a value of TYPE
+     formats as on output.  */
+  static unsigned type_max_digits (tree, int);
 };
 
-/* Description of a format directive.  */
+/* Adjust result upward to reflect the range ADJUST of values the
+   specified width or precision is known to be in.  When non-null,
+   TYPE denotes the type of the directive whose result is being
+   adjusted, BASE gives the base of the directive (octal, decimal,
+   or hex), and ADJ denotes the additional adjustment to the LIKELY
+   counter that may need to be added when ADJUST is a range.  */
+
+fmtresult&
+fmtresult::adjust_for_width_or_precision (const HOST_WIDE_INT adjust[2],
+					  tree type /* = NULL_TREE */,
+					  unsigned base /* = 0 */,
+					  unsigned adj /* = 0 */)
+{
+  bool minadjusted = false;
+
+  /* Adjust the minimum and likely counters.  */
+  if (0 <= adjust[0])
+    {
+      if (range.min < (unsigned HOST_WIDE_INT)adjust[0])
+	{
+	  range.min = adjust[0];
+	  minadjusted = true;
+	}
+
+      /* Adjust the likely counter.  */
+      if (range.likely < range.min)
+	range.likely = range.min;
+    }
+  else if (adjust[0] == target_int_min ()
+	   && (unsigned HOST_WIDE_INT)adjust[1] == target_int_max ())
+    {
+      knownrange = false;
+    }
+
+  /* Adjust the maximum counter.  */
+  if (0 < adjust[1])
+    {
+      if (range.max < (unsigned HOST_WIDE_INT)adjust[1])
+	{
+	  range.max = adjust[1];
+
+	  /* Set KNOWNRANGE if both the minimum and maximum have been
+	     adjusted.  Otherwise leave it at what it was before.  */
+	  knownrange = minadjusted;
+	}
+    }
+
+  if (warn_format_length > 1 && type)
+    {
+      /* For large non-constant width or precision whose range spans
+	 the maximum number of digits produced by the directive for
+	 any argument, set the likely number of bytes to be at most
+	 the number digits plus other adjustment determined by the
+	 caller (one for sign or two for the hexadecimal "0x"
+	 prefix).  */
+      unsigned dirdigs = type_max_digits (type, base);
+      if (adjust[0] < dirdigs && dirdigs < adjust[1]
+	  && range.likely < dirdigs)
+	range.likely = dirdigs + adj;
+    }
+  else if (range.likely < (range.min ? range.min : 1))
+    {
+      /* Conservatively, set LIKELY to at least MIN but no less than
+	 1 unless MAX is zero.  */
+      range.likely = (range.min
+		      ? range.min
+		      : range.max ? 1 : 0);
+    }
+
+  /* Finally adjust the unlikely counter to be at least as large as
+     the maximum.  */
+  if (range.unlikely < range.max)
+    range.unlikely = range.max;
+
+  return *this;
+}
+
+/* Return the maximum number of digits a value of TYPE formats in
+   BASE on output, not counting base prefix .  */
+
+unsigned
+fmtresult::type_max_digits (tree type, int base)
+{
+  unsigned prec = TYPE_PRECISION (type);
+  if (base == 8)
+    return (prec + 2) / 3;
+
+  if (base == 16)
+    return prec / 4;
+
+  /* Decimal approximation: yields 3, 5, 10, and 20 for precision
+     of 8, 16, 32, and 64 bits.  */
+  return prec * 301 / 1000 + 1;
+}
+
+/* Description of a format directive.  A directive is either a plain
+   string or a conversion specification that starts with '%'.  */
 
 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;
@@ -483,16 +610,11 @@  struct directive
   /* 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.  */
-  tree star_width;
-  /* Precision specified via the asterisk.  */
-  tree star_precision;
+  /* The range of values of the specified width, or -1 if not specified.  */
+  HOST_WIDE_INT width[2];
+  /* The range of values of the specified precision, or -1 if not
+     specified.  */
+  HOST_WIDE_INT prec[2];
 
   /* Length modifier.  */
   format_lengths modifier;
@@ -500,15 +622,6 @@  struct directive
   /* 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;
@@ -540,6 +653,30 @@  struct directive
     flags[c / (CHAR_BIT * sizeof *flags)]
       &= ~(1U << (c % (CHAR_BIT * sizeof *flags)));
   }
+
+  /* Set both bounds of the width range to VAL.  */
+  void set_width (HOST_WIDE_INT val)
+  {
+    width[0] = width[1] = val;
+  }
+
+  /* Set the width range according to ARG.  */
+  void set_width (tree arg)
+  {
+    get_int_range (arg, integer_type_node, width, width + 1, true, 0);
+  }
+
+  /* Set both bounds of the precision range to VAL.  */
+  void set_precision (HOST_WIDE_INT val)
+  {
+    prec[0] = prec[1] = val;
+  }
+
+  /* Set the precision range according to ARG.  */
+  void set_precision (tree arg)
+  {
+    get_int_range (arg, integer_type_node, prec, prec + 1, false, -1);
+  }
 };
 
 /* Return the logarithm of X in BASE.  */
@@ -613,7 +750,7 @@  tree_digits (tree x, int base, bool plus, bool prefix)
 }
 
 /* Given the formatting result described by RES and NAVAIL, the number
-   of available in the destination, return the number of bytes remaining
+   of available in the destination, return the range of bytes remaining
    in the destination.  */
 
 static inline result_range
@@ -623,68 +760,26 @@  bytes_remaining (unsigned HOST_WIDE_INT navail, const format_result &res)
 
   if (HOST_WIDE_INT_MAX <= navail)
     {
-      range.min = range.max = navail;
+      range.min = range.max = range.likely = range.unlikely = navail;
       return range;
     }
 
-  if (res.number_chars < navail)
-    {
-      range.min = range.max = navail - res.number_chars;
-    }
-  else if (res.number_chars_min < navail)
-    {
-      range.max = navail - res.number_chars_min;
-    }
-  else
-    range.max = 0;
-
-  if (res.number_chars_max < navail)
-    range.min = navail - res.number_chars_max;
-  else
-    range.min = 0;
+  /* The lower bound of the available range is the available size
+     minus the maximum output size, and the upper bound is the size
+     minus the minimum.  */
+  range.max = res.range.min < navail ? navail - res.range.min : 0;
 
-  return range;
-}
-
-/* Given the formatting result described by RES and NAVAIL, the number
-   of available in the destination, return the minimum number of bytes
-   remaining in the destination.  */
-
-static inline unsigned HOST_WIDE_INT
-min_bytes_remaining (unsigned HOST_WIDE_INT navail, const format_result &res)
-{
-  if (HOST_WIDE_INT_MAX <= navail)
-    return navail;
+  range.likely = res.range.likely < navail ? navail - res.range.likely : 0;
 
-  if (1 < warn_format_length || res.knownrange)
-    {
-      /* At level 2, or when all directives output an exact number
-	 of bytes or when their arguments were bounded by known
-	 ranges, use the greater of the two byte counters if it's
-	 valid to compute the result.  */
-      if (res.number_chars_max < HOST_WIDE_INT_MAX)
-	navail -= res.number_chars_max;
-      else if (res.number_chars < HOST_WIDE_INT_MAX)
-	navail -= res.number_chars;
-      else if (res.number_chars_min < HOST_WIDE_INT_MAX)
-	navail -= res.number_chars_min;
-    }
+  if (res.range.max < HOST_WIDE_INT_MAX)
+    range.min = res.range.max < navail ? navail - res.range.max : 0;
   else
-    {
-      /* At level 1 use the smaller of the byte counters to compute
-	 the result.  */
-      if (res.number_chars < HOST_WIDE_INT_MAX)
-	navail -= res.number_chars;
-      else if (res.number_chars_min < HOST_WIDE_INT_MAX)
-	navail -= res.number_chars_min;
-      else if (res.number_chars_max < HOST_WIDE_INT_MAX)
-	navail -= res.number_chars_max;
-    }
+    range.min = range.likely;
 
-  if (navail > HOST_WIDE_INT_MAX)
-    navail = 0;
+  range.unlikely = (res.range.unlikely < navail
+		    ? navail - res.range.unlikely : 0);
 
-  return navail;
+  return range;
 }
 
 /* Description of a call to a formatted function.  */
@@ -729,10 +824,7 @@  struct pass_sprintf_length::call_info
 static fmtresult
 format_percent (const directive &, tree)
 {
-  fmtresult res;
-  res.argmin = res.argmax = NULL_TREE;
-  res.range.min = res.range.max = 1;
-  res.bounded = res.knownrange = res.constant = true;
+  fmtresult res (1);
   return res;
 }
 
@@ -777,57 +869,94 @@  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.  */
+/* Determine the range [*PMIN, *PMAX] that the expression ARG of TYPE
+   is in.  Return true when the range is a subrange of that of TYPE.
+   Whn ARG is null it is as if it had the full range of TYPE.
+   When ABSOLUTE is true the range reflects the absolute value of
+   the argument.  When ABSOLUTE is false, negative bounds of
+   the determined range are replaced with NEGBOUND.  */
 
-static void
-get_width_and_precision (const directive &spec,
-			 HOST_WIDE_INT *pwidth, HOST_WIDE_INT *pprec)
+static bool
+get_int_range (tree arg, tree type, HOST_WIDE_INT *pmin, HOST_WIDE_INT *pmax,
+	       bool absolute, HOST_WIDE_INT negbound)
 {
-  HOST_WIDE_INT width = spec.have_width ? spec.width : 0;
-  HOST_WIDE_INT prec = spec.have_precision ? spec.precision : -1;
+  bool knownrange = false;
 
-  if (spec.star_width)
+  if (!arg)
+    {
+      *pmin = (TYPE_UNSIGNED (type)
+	       ? tree_to_uhwi (TYPE_MIN_VALUE (type))
+	       : tree_to_shwi (TYPE_MIN_VALUE (type)));
+      *pmax = tree_to_uhwi (TYPE_MAX_VALUE (type));
+    }
+  else if (TREE_CODE (arg) == INTEGER_CST)
     {
-      if (TREE_CODE (spec.star_width) == INTEGER_CST)
+      /* For a constant argument return its value adjusted as specified
+	 by NEGATIVE and NEGBOUND and return true to indicate that the
+	 result is known.  */
+      *pmin = tree_fits_shwi_p (arg) ? tree_to_shwi (arg) : tree_to_uhwi (arg);
+      *pmax = *pmin;
+      knownrange = true;
+    }
+  else
+    {
+      /* True if the argument's range cannot be determined.  */
+      bool unknown = true;
+
+      type = TREE_TYPE (arg);
+
+      if (TREE_CODE (arg) == SSA_NAME
+	  && TREE_CODE (type) == INTEGER_TYPE)
 	{
-	  width = tree_to_shwi (spec.star_width);
-	  if (width < 0)
+	  /* Try to determine the range of values of the integer argument.  */
+	  wide_int min, max;
+	  enum value_range_type range_type = get_range_info (arg, &min, &max);
+	  if (range_type == VR_RANGE)
 	    {
-	      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;
+	      HOST_WIDE_INT type_min
+		= (TYPE_UNSIGNED (type)
+		   ? tree_to_uhwi (TYPE_MIN_VALUE (type))
+		   : tree_to_shwi (TYPE_MIN_VALUE (type)));
+
+	      HOST_WIDE_INT type_max = tree_to_uhwi (TYPE_MAX_VALUE (type));
+
+	      *pmin = min.to_shwi ();
+	      *pmax = max.to_shwi ();
+
+	      /* Return true if the adjusted range is a subrange of
+		 the full range of the argument's type.  */
+	      knownrange = type_min < *pmin || *pmax < type_max;
+
+	      unknown = false;
 	    }
 	}
-      else
-	width = HOST_WIDE_INT_MIN;
+
+      /* Handle an argument with an unknown range as if none had been
+	 provided.  */
+      if (unknown)
+	return get_int_range (NULL_TREE, type, pmin, pmax, absolute, negbound);
     }
 
-  if (spec.star_precision)
+  /* Adjust each bound as specified by ABSOLUTE and NEGBOUND.  */
+  if (absolute)
     {
-      if (TREE_CODE (spec.star_precision) == INTEGER_CST)
+      if (*pmin < 0)
 	{
-	  prec = tree_to_shwi (spec.star_precision);
-	  if (prec < 0)
-	    prec = -1;
+	  if (*pmin == *pmax)
+	    *pmin = *pmax = -*pmin;
+	  else
+	    {
+	      HOST_WIDE_INT tmp = -*pmin;
+	      *pmin = 0;
+	      if (*pmax < tmp)
+		*pmax = tmp;
+	    }
 	}
-      else
-	prec = HOST_WIDE_INT_MIN;
     }
+  else if (*pmin < negbound)
+    *pmin = negbound;
 
-  *pwidth = width;
-  *pprec = prec;
+  return knownrange;
 }
 
 /* With the range [*ARGMIN, *ARGMAX] of an integer directive's actual
@@ -899,27 +1028,56 @@  adjust_range_for_overflow (tree dirtype, tree *argmin, tree *argmax)
    functions).  */
 
 static fmtresult
-format_integer (const directive &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);
+  /* Base to format the number in.  */
+  int base;
 
-  bool sign = spec.specifier == 'd' || spec.specifier == 'i';
+  /* True when a conversion is preceded by a prefix indicating the base
+     of the argument (octal or hexadecimal).  */
+  bool maybebase = dir.get_flag ('#');
+
+  /* True when a signed conversion is preceded by a sign or space.  */
+  bool maybesign = false;
+
+  /* True for signed conversions (i.e., 'd' and 'i').  */
+  bool sign = false;
+
+  switch (dir.specifier)
+    {
+    case 'd':
+    case 'i':
+      /* Space and '+' are  only meaningful for signed conversions.  */
+      maybesign = dir.get_flag (' ') | dir.get_flag ('+');
+      sign = true;
+      base = 10;
+      break;
+    case 'u':
+      base = 10;
+      break;
+    case 'o':
+      base = 8;
+      break;
+    case 'X':
+    case 'x':
+      base = 16;
+      break;
+    default:
+      gcc_unreachable ();
+    }
 
   /* 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;
@@ -979,38 +1137,9 @@  format_integer (const directive &spec, tree arg)
     {
       /* When a constant argument has been provided use its value
 	 rather than type to determine the length of the output.  */
+      fmtresult res;
 
-      /* Base to format the number in.  */
-      int base;
-
-      /* True when a signed conversion is preceded by a sign or space.  */
-      bool maybesign = false;
-
-      switch (spec.specifier)
-	{
-	case 'd':
-	case 'i':
-	  /* Space and '+' are  only meaningful for signed conversions.  */
-	  maybesign = spec.get_flag (' ') | spec.get_flag ('+');
-	  base = 10;
-	  break;
-	case 'u':
-	  base = 10;
-	  break;
-	case 'o':
-	  base = 8;
-	  break;
-	case 'X':
-	case 'x':
-	  base = 16;
-	  break;
-	default:
-	  gcc_unreachable ();
-	}
-
-      HOST_WIDE_INT len;
-
-      if ((prec == HOST_WIDE_INT_MIN || prec == 0) && integer_zerop (arg))
+      if ((dir.prec[0] <= 0 && dir.prec[1] >= 0) && integer_zerop (arg))
 	{
 	  /* As a special case, a precision of zero with a zero argument
 	     results in zero bytes except in base 8 when the '#' flag is
@@ -1019,46 +1148,36 @@  format_integer (const directive &spec, tree arg)
 	     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);
+	  res.range.min = ((base == 8 && dir.get_flag ('#')) || maybesign);
+	  if (res.range.min == 0 && dir.prec[0] != dir.prec[1])
+	    {
+	      res.range.max = 1;
+	      res.range.likely = 1;
+	    }
+	  else
+	    {
+	      res.range.max = res.range.min;
+	      res.range.likely = res.range.min;
+	    }
 	}
       else
 	{
 	  /* Convert the argument to the type of the directive.  */
 	  arg = fold_convert (dirtype, arg);
 
-	  /* True when a conversion is preceded by a prefix indicating the base
-	     of the argument (octal or hexadecimal).  */
-	  bool maybebase = spec.get_flag ('#');
-	  len = tree_digits (arg, base, maybesign, maybebase);
-
-	  if (len < prec)
-	    len = prec;
+	  res.range.min = tree_digits (arg, base, maybesign, maybebase);
+	  res.range.max = res.range.min;	
+	  res.range.likely = res.range.min;
 	}
 
-      if (len < width)
-	len = width;
-
-      /* The minimum and maximum number of bytes produced by the directive.  */
-      fmtresult res;
-
-      res.range.min = len;
-
-      /* The upper bound of the number of bytes is unlimited when either
-	 width or precision is specified but its value is unknown, and
-	 the same as the lower bound otherwise.  */
-      if (width == HOST_WIDE_INT_MIN || prec == HOST_WIDE_INT_MIN)
-	{
-	  res.range.max = HOST_WIDE_INT_MAX;
-	}
-      else
-	{
-	  res.range.max = len;
-	  res.bounded = true;
-	  res.constant = true;
-	  res.knownrange = true;
-	  res.bounded = true;
-	}
+      res.range.unlikely = res.range.max;
 
+      /* Bump up the counters if WIDTH is greater than LEN.  */
+      res.adjust_for_width_or_precision (dir.width, dirtype, base,
+					 (sign | maybebase) + (base == 16));
+      /* Bump up the counters again if PRECision is greater still.  */
+      res.adjust_for_width_or_precision (dir.prec, dirtype, base,
+					 (sign | maybebase) + (base == 16));
       return res;
     }
   else if (TREE_CODE (TREE_TYPE (arg)) == INTEGER_TYPE
@@ -1073,10 +1192,6 @@  format_integer (const directive &spec, tree arg)
 
   fmtresult res;
 
-  /* The result is bounded unless width or precision has been specified
-     whose value is unknown.  */
-  res.bounded = width != HOST_WIDE_INT_MIN && prec != HOST_WIDE_INT_MIN;
-
   /* Using either the range the non-constant argument is in, or its
      type (either "formal" or actual), create a range of values that
      constrain the length of output given the warning level.  */
@@ -1124,7 +1239,7 @@  format_integer (const directive &spec, tree arg)
 	      if (code == INTEGER_CST)
 		{
 		  arg = gimple_assign_rhs1 (def);
-		  return format_integer (spec, arg);
+		  return format_integer (dir, arg);
 		}
 
 	      if (code == NOP_EXPR)
@@ -1144,10 +1259,11 @@  format_integer (const directive &spec, tree arg)
 	 or one whose value range cannot be determined, create a T_MIN
 	 constant if the argument's type is signed and T_MAX otherwise,
 	 and use those to compute the range of bytes that the directive
-	 can output.  When precision is specified but unknown, use zero
-	 as the minimum since it results in no bytes on output (unless
-	 width is specified to be greater than 0).  */
-      argmin = build_int_cst (argtype, prec && prec != HOST_WIDE_INT_MIN);
+	 can output.  When precision may be zero, use zero as the minimum
+	 since it results in no bytes on output (unless width is specified
+	 to be greater than 0).  */
+      bool zero = dir.prec[0] <= 0 && dir.prec[1] >= 0;
+      argmin = build_int_cst (argtype, !zero);
 
       int typeprec = TYPE_PRECISION (dirtype);
       int argprec = TYPE_PRECISION (argtype);
@@ -1202,8 +1318,8 @@  format_integer (const directive &spec, tree arg)
       /* 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
     {
@@ -1212,17 +1328,10 @@  format_integer (const directive &spec, tree arg)
 	 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
-     (e.g., when it's within some range) or when the minimum and maximum
-     are the same.  That can happen here for example when the specified
-     width is as wide as the greater of MIN and MAX, as would be the case
-     with sprintf (d, "%08x", x) with a 32-bit integer x.  */
-  res.bounded |= res.range.min == res.range.max;
-
   if (res.range.max < res.range.min)
     {
       unsigned HOST_WIDE_INT tmp = res.range.max;
@@ -1230,6 +1339,13 @@  format_integer (const directive &spec, tree arg)
       res.range.min = tmp;
     }
 
+  res.range.likely = res.knownrange ? res.range.max : res.range.min;
+  res.range.unlikely = res.range.max;
+  res.adjust_for_width_or_precision (dir.width, dirtype, base,
+				     (sign | maybebase) + (base == 16));
+  res.adjust_for_width_or_precision (dir.prec, dirtype, base,
+				     (sign | maybebase) + (base == 16));
+
   return res;
 }
 
@@ -1255,15 +1371,26 @@  get_mpfr_format_length (mpfr_ptr x, const char *flags, HOST_WIDE_INT prec,
   fmtstr[len + 5] = spec;
   fmtstr[len + 6] = '\0';
 
-  /* Avoid passing negative precisions with larger magnitude to MPFR
-     to avoid exposing its bugs.  (A negative precision is supposed
-     to be ignored.)  */
-  if (prec < 0)
-    prec = -1;
+  spec = TOUPPER (spec);
+  if (spec == 'E' || spec == 'F')
+    {
+      /* For %e, specify the precision explicitly since mpfr_sprintf
+	 does its own thing just to be different (see MPFR bug 21088).  */
+      if (prec < 0)
+	prec = 6;
+    }
+  else
+    {
+      /* Avoid passing negative precisions with larger magnitude to MPFR
+	 to avoid exposing its bugs.  (A negative precision is supposed
+	 to be ignored.)  */
+      if (prec < 0)
+	prec = -1;
+    }
 
   HOST_WIDE_INT p = prec;
 
-  if (TOUPPER (spec) == 'G')
+  if (spec == 'G')
     {
       /* For G/g, precision gives the maximum number of significant
 	 digits which is bounded by LDBL_MAX_10_EXP, or, for a 128
@@ -1311,18 +1438,14 @@  format_floating_max (tree type, char spec, HOST_WIDE_INT prec)
   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
      round-to-nearest mode.  */
   mpfr_t x;
   mpfr_init2 (x, rfmt->p);
-  mpfr_from_real (x, &rv, GMP_RNDN);
+  mpfr_from_real (x, &rv, MPFR_RNDN);
 
   /* Return a value one greater to account for the leading minus sign.  */
   return 1 + get_mpfr_format_length (x, "", prec, spec, 'D');
@@ -1334,13 +1457,12 @@  format_floating_max (tree type, char spec, HOST_WIDE_INT prec)
    is used when the directive argument or its value isn't known.  */
 
 static fmtresult
-format_floating (const directive &spec, HOST_WIDE_INT width,
-		 HOST_WIDE_INT prec)
+format_floating (const directive &dir)
 {
   tree type;
   bool ldbl = false;
 
-  switch (spec.modifier)
+  switch (dir.modifier)
     {
     case FMT_LEN_l:
     case FMT_LEN_none:
@@ -1379,42 +1501,52 @@  format_floating (const directive &spec, HOST_WIDE_INT width,
       logexpdigs = ilog (expdigs, 10);
     }
 
-  switch (spec.specifier)
+  switch (dir.specifier)
     {
     case 'A':
     case 'a':
       {
 	/* The minimum output is "0x.p+0".  */
-	res.range.min = 6 + (prec > 0 ? prec : 0);
-	res.range.max = (width == INT_MIN
-			 ? HOST_WIDE_INT_MAX
-			 : format_floating_max (type, 'a', prec));
-
-	/* The output of "%a" is fully specified only when precision
-	   is explicitly specified and width isn't unknown.  */
-	res.bounded = INT_MIN != width && -1 < prec;
+	res.range.min = 6 + (dir.prec[0] > 0 ? dir.prec[0] : 0);
+	
+	res.range.max = format_floating_max (type, 'a', dir.prec[1]);
+	res.range.likely = res.range.min;
+
+	/* The unlikely maximum accounts for the longest multibyte
+	   decimal point character.  */
+	if (dir.prec[0] != dir.prec[1]
+            || dir.prec[0] == -1 || dir.prec[0] > 0)
+	  res.range.unlikely = res.range.max + target_mb_len_max () - 1;
+	else
+	  res.range.unlikely = res.range.max;
 	break;
       }
 
     case 'E':
     case 'e':
       {
-	bool sign = spec.get_flag ('+') || spec.get_flag (' ');
+	bool sign = dir.get_flag ('+') || dir.get_flag (' ');
 	/* The minimum output is "[-+]1.234567e+00" regardless
 	   of the value of the actual argument.  */
 	res.range.min = (sign
-			 + 1 /* unit */ + (prec < 0 ? 7 : prec ? prec + 1 : 0)
+			 + 1 /* unit */
+			 + (dir.prec[0] < 0
+			    ? 7 : dir.prec[0] ? dir.prec[0] + 1
+			    : 0)
 			 + 2 /* e+ */ + 2);
 	/* Unless width is uknown the maximum output is the minimum plus
 	   sign (unless already included), plus the difference between
 	   the minimum exponent of 2 and the maximum exponent for the type.  */
-	res.range.max = (width == INT_MIN
-			 ? HOST_WIDE_INT_M1U
-			 : res.range.min + !sign + logexpdigs - 2);
-
-	/* "%e" is fully specified and the range of bytes is bounded
-	   unless width is unknown.  */
-	res.bounded = INT_MIN != width;
+	res.range.max = res.range.min + !sign + logexpdigs - 2;
+	res.range.likely = res.range.min;
+
+	/* The unlikely maximum accounts for the longest multibyte
+	   decimal point character.  */
+	if (dir.prec[0] != dir.prec[1]
+            || dir.prec[0] == -1 || dir.prec[0] > 0)
+	  res.range.unlikely = res.range.max + target_mb_len_max () -1;
+	else
+	  res.range.unlikely = res.range.max;
 	break;
       }
 
@@ -1423,18 +1555,21 @@  format_floating (const directive &spec, HOST_WIDE_INT width,
       {
 	/* The minimum output is "1.234567" regardless of the value
 	   of the actual argument.  */
-	res.range.min = 2 + (prec < 0 ? 6 : prec);
+	res.range.min = 2 + (dir.prec[0] < 0 ? 6 : dir.prec[0]);
 
 	/* Compute the maximum just once.  */
 	const HOST_WIDE_INT f_max[] = {
-	  format_floating_max (double_type_node, 'f', prec),
-	  format_floating_max (long_double_type_node, 'f', prec)
+	  format_floating_max (double_type_node, 'f', dir.prec[1]),
+	  format_floating_max (long_double_type_node, 'f', dir.prec[1])
 	};
-	res.range.max = width == INT_MIN ? HOST_WIDE_INT_MAX : f_max [ldbl];
-
-	/* "%f" is fully specified and the range of bytes is bounded
-	   unless width is unknown.  */
-	res.bounded = INT_MIN != width;
+	res.range.max = f_max [ldbl];
+	res.range.likely = res.range.min;
+
+	/* The unlikely maximum accounts for the longest multibyte
+	   decimal point character.  */
+	if (dir.prec[0] != dir.prec[1]
+            || dir.prec[0] == -1 || dir.prec[0] > 0)
+	  res.range.unlikely = res.range.max + target_mb_len_max () - 1;
 	break;
       }
     case 'G':
@@ -1445,14 +1580,15 @@  format_floating (const directive &spec, HOST_WIDE_INT width,
 
 	/* Compute the maximum just once.  */
 	const HOST_WIDE_INT g_max[] = {
-	  format_floating_max (double_type_node, 'g', prec),
-	  format_floating_max (long_double_type_node, 'g', prec)
+	  format_floating_max (double_type_node, 'g', dir.prec[1]),
+	  format_floating_max (long_double_type_node, 'g', dir.prec[1])
 	};
-	res.range.max = width == INT_MIN ? HOST_WIDE_INT_MAX : g_max [ldbl];
+	res.range.max = g_max [ldbl];
+	res.range.likely = g_max [ldbl];
 
-	/* "%g" is fully specified and the range of bytes is bounded
-	   unless width is unknown.  */
-	res.bounded = INT_MIN != width;
+	/* The unlikely maximum accounts for the longest multibyte
+	   decimal point character.  */
+	res.range.unlikely = res.range.max + target_mb_len_max () - 1;
 	break;
       }
 
@@ -1460,14 +1596,8 @@  format_floating (const directive &spec, HOST_WIDE_INT width,
       return fmtresult ();
     }
 
-  if (width > 0)
-    {
-      if (res.range.min < (unsigned)width)
-	res.range.min = width;
-      if (res.range.max < (unsigned)width)
-	res.range.max = width;
-    }
-
+  /* Bump up the byte counters if WIDTH is greater.  */
+  res.adjust_for_width_or_precision (dir.width);
   return res;
 }
 
@@ -1476,120 +1606,109 @@  format_floating (const directive &spec, HOST_WIDE_INT width,
    floating argument ARG.  */
 
 static fmtresult
-format_floating (const directive &spec, tree arg)
+format_floating (const directive &dir, tree arg)
 {
-  /* Set WIDTH to -1 when it's not specified, to INT_MIN when it is
-     specified by the asterisk to an unknown value, and otherwise to
-     a non-negative value corresponding to the specified width.  */
-  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[0], dir.prec[1] };
 
-  if (spec.have_width)
-    width = spec.width;
-  else if (spec.star_width)
+  if (TOUPPER (dir.specifier) == 'A')
     {
-      if (TREE_CODE (spec.star_width) == INTEGER_CST)
+      /* For %a, leave the minimum precision unspecified to let
+	 MFPR trim trailing zeros (as it and many other systems
+	 including Glibc happen to do) and set the maximum
+	 precision to reflect what it would be with trailing zeros
+	 present (as Solaris and derived systems do).  */
+      if (prec[0] < 0)
+	prec[0] = -1;
+      if (prec[1] < 0)
 	{
-	  width = tree_to_shwi (spec.star_width);
-	  if (width < 0)
-	    width = -width;
+	  unsigned fmtprec
+	    = (dir.modifier == FMT_LEN_L
+	       ? REAL_MODE_FORMAT (XFmode)->p
+	       : REAL_MODE_FORMAT (DFmode)->p);
+
+	  /* The precision of the IEEE 754 double format is 53.
+	     The precision of all other GCC binary double formats
+	     is 56 or less.  */
+	  prec[1] = fmtprec <= 56 ? 13 : 15;
 	}
-      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);
-      else
-	{
-	  /* FIXME: Handle non-constant precision.  */
-	  res.range.min = res.range.max = HOST_WIDE_INT_M1U;
-	  return res;
-	}
-    }
-  else if (res.constant && TOUPPER (spec.specifier) != 'A')
-    {
-      /* Specify the precision explicitly since mpfr_sprintf defaults
-	 to zero.  */
-      prec = 6;
-    }
+  /* The minimum and maximum number of bytes produced by the directive.  */
+  fmtresult res;
 
-  if (res.constant)
-    {
-      /* Set up an array to easily iterate over.  */
-      unsigned HOST_WIDE_INT* const minmax[] = {
-	&res.range.min, &res.range.max
-      };
+  /* Set up an array to easily iterate over.  */
+  unsigned HOST_WIDE_INT* const minmax[] = {
+    &res.range.min, &res.range.max
+  };
+
+  /* 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)));
 
-      /* 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)));
+  char fmtstr [40];
+  char *pfmt = fmtstr;
 
+  /* Append flags.  */
+  for (const char *pf = "-+ #0"; *pf; ++pf)
+    if (dir.get_flag (*pf))
+      *pfmt++ = *pf;
+
+  *pfmt = '\0';
+
+  for (int i = 0; i != sizeof minmax / sizeof *minmax; ++i)
+    {
       /* 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.  */
+	 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, GMP_RNDN);
-
-      char fmtstr [40];
-      char *pfmt = fmtstr;
+      mpfr_from_real (mpfrval, rvp, i ? MPFR_RNDU : MPFR_RNDD);
 
-      /* Append flags.  */
-      for (const char *pf = "-+ #0"; *pf; ++pf)
-	if (spec.get_flag (*pf))
-	  *pfmt++ = *pf;
+      /* 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];
 
-      *pfmt = '\0';
+      /* Format it and store the result in the corresponding member
+	 of the result struct.  */
+      *minmax[i] = get_mpfr_format_length (mpfrval, fmtstr, prec[i],
+					   dir.specifier, rndspec);
+    }
 
-      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;
-	}
+  if (res.range.max < res.range.min)
+    {
+      /* Swap the two bounds if rounding resulted in the maximum being
+	 less than the minimum.  */
+      unsigned HOST_WIDE_INT tmp = res.range.max;
+      res.range.max = res.range.min;
+      res.range.min = tmp;
+    }
 
-      /* The range of output is known even if the result isn't bounded.  */
-      if (width == INT_MIN)
-	{
-	  res.knownrange = false;
-	  res.range.max = HOST_WIDE_INT_MAX;
-	}
-      else
-	res.knownrange = true;
+  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 ());
+  /* For the same floating point constant use the longer output
+     as the likely maximum since with round to nearest either is
+     equally likely. */
+  res.range.likely = res.range.max;
+  res.range.unlikely = res.range.max;
 
-      return res;
+  if (prec[0] != 0 || prec[1] != 0)
+    {
+      /* Unless the precision is zero the output may include
+	 the decimal point which must be a single character up
+	 to MB_LEN_MAX in length.  This is overly conservative
+	 since in some conversions some constants result in no
+	 decimal point (e.g., in %g).  */
+      res.range.unlikely += target_mb_len_max () - 1;
     }
 
-  return format_floating (spec, width, prec);
+  res.adjust_for_width_or_precision (dir.width);
+  return res;
 }
 
 /* Return a FMTRESULT struct set to the lengths of the shortest and longest
@@ -1605,11 +1724,7 @@  get_string_length (tree str)
   if (tree slen = c_strlen (str, 1))
     {
       /* Simply return the length of the string.  */
-      fmtresult res;
-      res.range.min = res.range.max = tree_to_shwi (slen);
-      res.bounded = true;
-      res.constant = true;
-      res.knownrange = true;
+      fmtresult res (tree_to_shwi (slen));
       return res;
     }
 
@@ -1623,24 +1738,23 @@  get_string_length (tree str)
 
   if (lenrange [0] || lenrange [1])
     {
-      fmtresult res;
-
-      res.range.min = (tree_fits_uhwi_p (lenrange[0])
-		       ? tree_to_uhwi (lenrange[0]) : 1 < warn_format_length);
-      res.range.max = (tree_fits_uhwi_p (lenrange[1])
-		       ? tree_to_uhwi (lenrange[1]) : HOST_WIDE_INT_M1U);
+      fmtresult res (tree_fits_uhwi_p (lenrange[0])
+		     ? tree_to_uhwi (lenrange[0]) : 1 < warn_format_length,
+		     tree_fits_uhwi_p (lenrange[1])
+		     ? tree_to_uhwi (lenrange[1]) : HOST_WIDE_INT_M1U);
 
-      /* Set RES.BOUNDED to true if and only if all strings referenced
+      /* Set RES.KNOWNRANGE to true if and only if all strings referenced
 	 by STR are known to be bounded (though not necessarily by their
 	 actual length but perhaps by their maximum possible length).  */
-      res.bounded = res.range.max < target_int_max ();
-      res.knownrange = res.bounded;
-
-      /* Set RES.CONSTANT to false even though that may be overly
-	 conservative in rare cases like: 'x ? a : b' where a and
-	 b have the same lengths and consist of the same characters.  */
-      res.constant = false;
+      if (res.range.max < target_int_max ())
+	{
+	  res.knownrange = true;
+	  res.range.likely = res.range.max;
+	}
+      else
+	res.range.likely = res.range.min;
 
+      res.range.unlikely = res.range.max;
       return res;
     }
 
@@ -1648,215 +1762,513 @@  get_string_length (tree str)
 }
 
 /* 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 directive &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 : 1 < warn_format_length ? 6 : 1);
-
-  /* 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 : 1 < warn_format_length);
-
-  /* The result is bounded unless overriddden for a non-constant string
-     of an unknown length.  */
-  bool bounded = true;
+  res.knownrange = true;
 
-  if (spec.specifier == 'c')
+  if (dir.modifier == FMT_LEN_l)
     {
-      if (spec.modifier == FMT_LEN_l)
+      res.range.min = 0;
+      HOST_WIDE_INT min, max;
+      if (get_int_range (arg, integer_type_node, &min, &max, false, 0))
 	{
-	  /* 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 = 1 == warn_format_length ? !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;
+	  if (min == 0 && max == 0)
+	    {
+	      res.range.min = res.range.max = 0;
+	      res.range.likely = 0;
+	    }
+	  else if (0 < min && min < 128)
+	    {
+	      res.range.max = target_mb_len_max ();
+	      res.range.likely = 1;
+	    }
+	  else
+	    {
+	      res.range.max = target_mb_len_max ();
+	      res.range.likely = 2;
+	    }
 	}
       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;
+	  res.range.max = target_mb_len_max ();
+	  res.range.likely = 2;
 	}
+
+      res.range.unlikely = res.range.max;
     }
-  else   /* spec.specifier == 's' */
+  else
     {
-      /* 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 plain '%c' directive.  Its ouput is exactly 1.  */
+      res.range.min = res.range.max = 1;
+      res.range.likely = res.range.unlikely = 1;
+      res.knownrange = true;
+    }
 
-	  /* A '%s' directive with a string argument with constant length.  */
-	  res.range = slen.range;
+  /* Bump up the byte counters if WIDTH is greater.  */
+  return res.adjust_for_width_or_precision (dir.width);
+}
 
-	  /* 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;
+/* Return the minimum and maximum number of characters formatted
+   by the '%s' format directive and its wide character form for
+   the argument ARG.  ARG can be null (for functions such as
+   vsprinf).  */
 
-	  if (spec.modifier == FMT_LEN_l)
-	    {
-	      bounded = false;
+static fmtresult
+format_string (const directive &dir, tree arg)
+{
+  fmtresult res;
 
-	      if (warn_format_length > 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;
-		}
+  /* Compute the range the argument's length can be in.  */
+  fmtresult slen = get_string_length (arg);
+  if (slen.range.min == slen.range.max
+      && slen.range.min < HOST_WIDE_INT_MAX)
+    {
+      /* The argument is either a string constant or it refers
+	 to one of a number of strings of the same length.  */
 
-	      /* 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)
-	    {
-	      /* 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;
-	    }
+      /* A '%s' directive with a string argument with constant length.  */
+      res.range = slen.range;
 
-	  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)
+      if (dir.modifier == FMT_LEN_l)
+	{
+	  /* In the worst case the length of output of a wide string S
+	     is bounded by MB_LEN_MAX * wcslen (S). */
+	  res.range.max *= target_mb_len_max ();
+	  res.range.unlikely = res.range.max;
+	  /* It's likely that the the total length is not more that
+	     2 * wcslen (S).*/
+	  res.range.likely = res.range.min * 2;
+
+	  if (0 <= dir.prec[1]
+	      && (unsigned HOST_WIDE_INT)dir.prec[1] < res.range.max)
 	    {
-	      /* When precision is specified but not known the lower
-		 bound is assumed to be as low as zero.  */
-	      res.range.min = 0;
+	      res.range.max = dir.prec[1];
+	      res.range.likely = dir.prec[1];
+	      res.range.unlikely = dir.prec[1];
 	    }
-	}
-      else if (arg && integer_zerop (arg))
-	{
-	  /* Handle null pointer argument.  */
 
-	  fmtresult res;
+	  if (dir.prec[0] < 0 && dir.prec[1] > -1)
+	    res.range.min = 0;
+	  else if (0 <= dir.prec[0])
+	    res.range.likely = dir.prec[0];
+
+	  /* Even a non-empty wide character string need not convert into
+	     any bytes.  */
 	  res.range.min = 0;
-	  res.range.max = HOST_WIDE_INT_MAX;
-	  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.  */
+	  res.knownrange = true;
+
+	  if (dir.prec[0] < 0 && dir.prec[1] > -1)
+	    res.range.min = 0;
+	  else if ((unsigned HOST_WIDE_INT)dir.prec[0] < res.range.min)
+	    res.range.min = dir.prec[0];
+
+	  if ((unsigned HOST_WIDE_INT)dir.prec[1] < res.range.max)
+	    {
+	      res.range.max = dir.prec[1];
+	      res.range.likely = dir.prec[1];
+	      res.range.unlikely = dir.prec[1];
+	    }
+	}
+    }
+  else if (arg && integer_zerop (arg))
+    {
+      /* Handle null pointer argument.  */
+
+      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.  */
 
-	  if (0 <= prec)
+      if (0 <= dir.prec[0])
+	{
+	  if (slen.range.min >= target_int_max ())
+	    slen.range.min = 0;
+	  else if ((unsigned HOST_WIDE_INT)dir.prec[0] < slen.range.min)
 	    {
-	      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;
+	      slen.range.min = dir.prec[0];
+	      slen.range.likely = slen.range.min;
 	    }
-	  else if (slen.range.min >= target_int_max ())
+
+	  if ((unsigned HOST_WIDE_INT)dir.prec[1] < slen.range.max
+	      || slen.range.max >= target_int_max ())
 	    {
-	      slen.range.min = max_bytes_for_unknown_str;
-	      slen.range.max = max_bytes_for_unknown_str;
-	      bounded = false;
+	      slen.range.max = dir.prec[1];
+	      slen.range.likely = slen.range.max;
 	    }
+	}
+      else if (slen.range.min >= target_int_max ())
+	{
+	  slen.range.min = 0;
+	  slen.range.max = HOST_WIDE_INT_MAX;
+	  slen.range.likely = 1;
+	}
 
-	  res.range = slen.range;
+      slen.range.unlikely = slen.range.max;
 
-	  /* 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;
-	}
+      res.range = slen.range;
+      res.knownrange = slen.knownrange;
     }
 
-  /* Adjust the lengths for field width.  */
-  if (0 < width)
-    {
-      if (res.range.min < (unsigned HOST_WIDE_INT)width)
-	res.range.min = width;
+  /* Bump up the byte counters if WIDTH is greater.  */
+  return res.adjust_for_width_or_precision (dir.width);
+}
 
-      if (res.range.max < (unsigned HOST_WIDE_INT)width)
-	res.range.max = width;
+/* Format plain string (part of the format string itself).  */
 
-      /* Adjust BOUNDED if width happens to make them equal.  */
-      if (res.range.min == res.range.max && res.range.min < target_int_max ()
-	  && bounded)
-	res.bounded = true;
+static fmtresult
+format_plain (const directive &dir, tree)
+{
+  fmtresult res (dir.len);
+  return res;
+}
+
+static bool
+should_warn_p (const result_range &avail, const result_range &result)
+{
+  if (result.max <= avail.min)
+    {
+      /* The least amount of space remaining in the destination is big
+	 enough for the longest output.  */
+      return false;
     }
 
-  /* When precision is specified the range of characters on output
-     is known to be bounded by it.  */
-  if (-1 < width && -1 < prec)
-    res.knownrange = true;
+  if (1 == warn_format_length
+      && result.likely <= avail.likely)
+    {
+      /* The likely amount of space remaining in the destination is big
+	 enough for the likely output.  */
+      return false;
+    }
 
-  return res;
+  if (warn_format_length == 2
+      && result.likely <= avail.min
+      && (result.max <= avail.min
+	  || result.max > HOST_WIDE_INT_MAX))
+    {
+      /* The minimum amount of space remaining in the destination is big
+	 enough for the longest output.  */
+      return false;
+    }
+
+  return true;
 }
 
+/* At format string location describe by DIRLOC in a call described
+   by INFO, issue a warning for a directive DIR whose output may be
+   in excess of the available space AVAIL_RANGE in the destination
+   given the formatting result FMTRES.  This function does nothing
+   except decide whether to issue a warning for a possible write
+   past the end or truncation and, if so, format the warning.
+   Return true if a warning has been issued.  */
 
-static fmtresult
-format_plain (const directive &spec, tree)
+static bool
+maybe_warn (substring_loc &dirloc, source_range *pargrange,
+	    const pass_sprintf_length::call_info &info,
+	    const result_range &avail_range, const result_range &res,
+	    const directive &dir)
 {
-  fmtresult res;
-  res.argmin = res.argmax = NULL_TREE;
-  res.range.min = res.range.max = spec.len;
-  res.bounded = res.knownrange = res.constant = true;
-  return res;
+  if (!should_warn_p (avail_range, res))
+    return false;
+
+  /* A warning will definitely be issued below.  */
+
+  /* The maximum byte count to reference in the warning.  Larger counts
+     imply that the upper bound is unknown (and could be anywhere between
+     RES.MIN + 1 and SIZE_MAX / 2) are printed as "N or more bytes" rather
+     than "between N and X" where X is some huge number.  */
+  unsigned HOST_WIDE_INT maxbytes = target_dir_max ();
+
+  /* True when there is enough room in the destination for the least
+     amount of a directive's output but not enough for its likely or
+     maximum output.  */
+  bool maybe = (res.min <= avail_range.max
+		&& (avail_range.min < res.likely
+		    || (res.max < HOST_WIDE_INT_MAX
+			&& avail_range.min < res.max)));
+
+  if (avail_range.min == avail_range.max)
+    {
+      /* The size of the destination region is exact.  */
+      unsigned HOST_WIDE_INT navail = avail_range.max;
+
+      if (*dir.beg != '%')
+	{
+	  /* For plain character directives (i.e., the format string itself)
+	     but not others, point the caret at the first character that's
+	     past the end of the destination.  */
+	  dirloc.set_caret_index (dirloc.get_caret_idx () + navail);
+	}
+
+      if (*dir.beg == '\0')
+	{
+	  /* This is the terminating nul.  */
+	  gcc_assert (res.min == 1 && res.min == res.max);
+
+	  const char *fmtstr
+	    = (info.bounded
+	       ? (maybe
+		  ? G_("%qE output may be truncated before the last format "
+		       "character")
+		  : G_("%qE output truncated before the last format character"))
+	       : (maybe
+		  ? G_("%qE may write a terminating nul past the end "
+		       "of the destination")
+		  : G_("%qE writing a terminating nul past the end "
+		       "of the destination")));
+
+	  return fmtwarn (dirloc, NULL, NULL, OPT_Wformat_length_, fmtstr,
+			  info.func);
+	}
+
+      if (res.min == res.max)
+	{
+	  const char* fmtstr
+	    = (res.min == 1
+	       ? (info.bounded
+		  ? (maybe
+		     ? G_("%<%.*s%> directive output may be truncated writing "
+			  "%wu byte into a region of size %wu")
+		     : G_("%<%.*s%> directive output truncated writing "
+			  "%wu byte into a region of size %wu"))
+		  : G_("%<%.*s%> directive writing %wu byte "
+		       "into a region of size %wu"))
+	       : (info.bounded
+		  ? (maybe
+		     ? G_("%<%.*s%> directive output may be truncated writing "
+			  "%wu bytes into a region of size %wu")
+		     : G_("%<%.*s%> directive output truncated writing "
+			  "%wu bytes into a region of size %wu"))
+		  : G_("%<%.*s%> directive writing %wu bytes "
+		       "into a region of size %wu")));
+	  return fmtwarn (dirloc, pargrange, NULL,
+			  OPT_Wformat_length_, fmtstr,
+			  dir.len, dir.beg, res.min,
+			  navail);
+	}
+
+      if (0 == res.min && res.max < maxbytes)
+	{
+	  const char* fmtstr
+	    = (info.bounded
+	       ? (maybe
+		  ? G_("%<%.*s%> directive output may be truncated writing "
+		       "up to %wu bytes into a region of size %wu")
+		  : G_("%<%.*s%> directive output truncated writing "
+		       "up to %wu bytes into a region of size %wu"))
+	       : G_("%<%.*s%> directive writing up to %wu bytes "
+		    "into a region of size %wu"));
+	  return fmtwarn (dirloc, pargrange, NULL,
+			  OPT_Wformat_length_, fmtstr,
+			  dir.len, dir.beg,
+			  res.max, navail);
+	}
+
+      if (0 == res.min && maxbytes <= res.max)
+	{
+	  /* This is a special case to avoid issuing the potentially
+	     confusing warning:
+  	       writing 0 or more bytes into a region of size 0.  */
+	  const char* fmtstr
+	    = (info.bounded
+	       ? (maybe
+		  ? G_("%<%.*s%> directive output may be truncated writing "
+		       "likely %wu or more bytes into a region of size %wu")
+		  : G_("%<%.*s%> directive output truncated writing "
+		       "likely %wu or more bytes into a region of size %wu"))
+	       : G_("%<%.*s%> directive writing likely %wu or more bytes "
+		    "into a region of size %wu"));
+	  return fmtwarn (dirloc, pargrange, NULL,
+			  OPT_Wformat_length_, fmtstr,
+			  dir.len, dir.beg,
+			  res.likely, navail);
+	}
+
+      if (res.max < maxbytes)
+	{
+	  const char* fmtstr
+	    = (info.bounded
+	       ? (maybe
+		  ? G_("%<%.*s%> directive output may be truncated writing "
+		       "between %wu and %wu bytes into a region of size %wu")
+		  : G_("%<%.*s%> directive output truncated writing "
+		       "between %wu and %wu bytes into a region of size %wu"))
+	       : G_("%<%.*s%> directive writing between %wu and "
+		    "%wu bytes into a region of size %wu"));
+	  return fmtwarn (dirloc, pargrange, NULL,
+			  OPT_Wformat_length_, fmtstr,
+			  dir.len, dir.beg,
+			  res.min, res.max,
+			  navail);
+	}
+
+      const char* fmtstr
+	= (info.bounded
+	   ? (maybe
+	      ? G_("%<%.*s%> directive output may be truncated writing "
+		   "%wu or more bytes into a region of size %wu")
+	      : G_("%<%.*s%> directive output truncated writing "
+		   "%wu or more bytes into a region of size %wu"))
+	   : G_("%<%.*s%> directive writing %wu or more bytes "
+		"into a region of size %wu"));
+      return fmtwarn (dirloc, pargrange, NULL,
+		      OPT_Wformat_length_, fmtstr,
+		      dir.len, dir.beg,
+		      res.min, navail);
+    }
+
+  /* The size of the destination region is a range.  */
+
+  if (*dir.beg != '%')
+    {
+      unsigned HOST_WIDE_INT navail = avail_range.max;
+
+      /* For plain character directives (i.e., the format string itself)
+	 but not others, point the caret at the first character that's
+	 past the end of the destination.  */
+      dirloc.set_caret_index (dirloc.get_caret_idx () + navail);
+    }
+
+  if (*dir.beg == '\0')
+    {
+      gcc_assert (res.min == 1 && res.min == res.max);
+
+      const char *fmtstr
+	= (info.bounded
+	   ? (maybe
+	      ? G_("%qE output may be truncated before the last format "
+		   "character")
+	      : G_("%qE output truncated before the last format character"))
+	   : (maybe
+	      ? G_("%qE may write a terminating nul past the end "
+		   "of the destination")
+	      : G_("%qE writing a terminating nul past the end "
+		   "of the destination")));
+
+      return fmtwarn (dirloc, NULL, NULL, OPT_Wformat_length_, fmtstr,
+		      info.func);
+    }
+
+  if (res.min == res.max)
+    {
+      const char* fmtstr
+	= (res.min == 1
+	   ? (info.bounded
+	      ? (maybe
+		 ? G_("%<%.*s%> directive output may be truncated writing "
+		      "%wu byte into a region of size between %wu and %wu")
+		 : G_("%<%.*s%> directive output truncated writing "
+		      "%wu byte into a region of size between %wu and %wu"))
+	      : G_("%<%.*s%> directive writing %wu byte "
+		   "into a region of size between %wu and %wu"))
+	   : (info.bounded
+	      ? (maybe
+		 ? G_("%<%.*s%> directive output may be truncated writing "
+		      "%wu bytes into a region of size between %wu and %wu")
+		 : G_("%<%.*s%> directive output truncated writing "
+		      "%wu bytes into a region of size between %wu and %wu"))
+	      : G_("%<%.*s%> directive writing %wu bytes "
+		   "into a region of size between %wu and %wu")));
+
+      return fmtwarn (dirloc, pargrange, NULL,
+		      OPT_Wformat_length_, fmtstr,
+		      dir.len, dir.beg, res.min,
+		      avail_range.min, avail_range.max);
+    }
+
+  if (0 == res.min && res.max < maxbytes)
+    {
+      const char* fmtstr
+	= (info.bounded
+	   ? (maybe
+	      ? G_("%<%.*s%> directive output may be truncated writing "
+		   "up to %wu bytes into a region of size between "
+		   "%wu and %wu")
+	      : G_("%<%.*s%> directive output truncated writing "
+		   "up to %wu bytes into a region of size between "
+		   "%wu and %wu"))
+	   : G_("%<%.*s%> directive writing up to %wu bytes "
+		"into a region of size between %wu and %wu"));
+      return fmtwarn (dirloc, pargrange, NULL,
+		      OPT_Wformat_length_, fmtstr,
+		      dir.len, dir.beg, res.max,
+		      avail_range.min, avail_range.max);
+    }
+
+  if (0 == res.min && maxbytes <= res.max)
+    {
+      /* This is a special case to avoid issuing the potentially confusing
+	 warning:
+	   writing 0 or more bytes into a region of size between 0 and N.  */
+      const char* fmtstr
+	= (info.bounded
+	   ? (maybe
+	      ? G_("%<%.*s%> directive output may be truncated writing "
+		   "likely %wu or more bytes into a region of size between "
+		   "%wu and %wu")
+	      : G_("%<%.*s%> directive output truncated writing likely "
+		   "%wu or more bytes into a region of size between "
+		   "%wu and %wu"))
+	   : G_("%<%.*s%> directive writing likely %wu or more bytes "
+		"into a region of size between %wu and %wu"));
+      return fmtwarn (dirloc, pargrange, NULL,
+		      OPT_Wformat_length_, fmtstr,
+		      dir.len, dir.beg, res.likely,
+		      avail_range.min, avail_range.max);
+    }
+
+  if (res.max < maxbytes)
+    {
+      const char* fmtstr
+	= (info.bounded
+	   ? (maybe
+	      ? G_("%<%.*s%> directive output may be truncated writing "
+		   "between %wu and %wu bytes into a region of size "
+		   "between %wu and %wu")
+	      : G_("%<%.*s%> directive output truncated writing "
+		   "between %wu and %wu bytes into a region of size "
+		   "between %wu and %wu"))
+	   : G_("%<%.*s%> directive writing between %wu and "
+		"%wu bytes into a region of size between %wu and %wu"));
+      return fmtwarn (dirloc, pargrange, NULL,
+		      OPT_Wformat_length_, fmtstr,
+		      dir.len, dir.beg,
+		      res.min, res.max,
+		      avail_range.min, avail_range.max);
+    }
+
+  const char* fmtstr
+    = (info.bounded
+       ? (maybe
+	  ? G_("%<%.*s%> directive output may be truncated writing "
+	       "%wu or more bytes into a region of size between "
+	       "%wu and %wu")
+	  : G_("%<%.*s%> directive output truncated writing "
+	       "%wu or more bytes into a region of size between "
+	       "%wu and %wu"))
+       : G_("%<%.*s%> directive writing %wu or more bytes "
+	    "into a region of size between %wu and %wu"));
+  return fmtwarn (dirloc, pargrange, NULL,
+		  OPT_Wformat_length_, fmtstr,
+		  dir.len, dir.beg,
+		  res.min,
+		  avail_range.min, avail_range.max);
 }
 
 /* Compute the length of the output resulting from the conversion
@@ -1871,11 +2283,13 @@  format_directive (const pass_sprintf_length::call_info &info,
   /* Offset of the beginning of the directive from the beginning
      of the format string.  */
   size_t offset = dir.beg - info.fmtstr;
+  size_t start = offset;
+  size_t length = offset + dir.len - !!dir.len;
 
   /* Create a location for the whole directive from the % to the format
      specifier.  */
   substring_loc dirloc (info.fmtloc, TREE_TYPE (info.format),
-			offset, offset, offset + dir.len - 1);
+			offset, start, length);
 
   /* Also create a location range for the argument if possible.
      This doesn't work for integer literals or function calls.  */
@@ -1891,17 +2305,12 @@  format_directive (const pass_sprintf_length::call_info &info,
 
   /* Bail when there is no function to compute the output length,
      or when minimum length checking has been disabled.   */
-  if (!dir.fmtfunc || res->number_chars_min >= HOST_WIDE_INT_MAX)
+  if (!dir.fmtfunc || res->range.min >= HOST_WIDE_INT_MAX)
     return false;
 
-  /* Compute the (approximate) length of the formatted output.  */
+  /* Compute the range of lengths of the formatted output.  */
   fmtresult fmtres = dir.fmtfunc (dir, dir.arg);
 
-  /* The overall result is bounded and constant only if the output
-     of every directive is bounded and constant, respectively.  */
-  res->bounded &= fmtres.bounded;
-  res->constant &= fmtres.constant;
-
   /* Record whether the output of all directives is known to be
      bounded by some maximum, implying that their arguments are
      either known exactly or determined to be in a known range
@@ -1909,6 +2318,7 @@  format_directive (const pass_sprintf_length::call_info &info,
      they refer to.  */
   res->knownrange &= fmtres.knownrange;
 
+#if 1
   if (!fmtres.knownrange)
     {
       /* Only when the range is known, check it against the host value
@@ -1928,8 +2338,7 @@  format_directive (const pass_sprintf_length::call_info &info,
 	     to determine the maximum number of characters (for example
 	     for wide characters or wide character strings) but continue
 	     tracking the minimum number of characters.  */
-	  res->number_chars_max = HOST_WIDE_INT_M1U;
-	  res->number_chars = HOST_WIDE_INT_M1U;
+	  res->range.max = HOST_WIDE_INT_M1U;
 	}
 
       if (fmtres.range.min > target_dir_max ())
@@ -1938,10 +2347,10 @@  format_directive (const pass_sprintf_length::call_info &info,
 	     even the minimum number of characters (it shouldn't happen
 	     except in an error) but keep tracking the minimum and maximum
 	     number of characters.  */
-	  res->number_chars = HOST_WIDE_INT_M1U;
 	  return true;
 	}
     }
+#endif
 
   const char *dirbeg = dir.beg;
   int dirlen = dir.len;
@@ -1955,158 +2364,54 @@  format_directive (const pass_sprintf_length::call_info &info,
 
       /* Don't bother processing the rest of the format string.  */
       res->warned = true;
-      res->number_chars = HOST_WIDE_INT_M1U;
-      res->number_chars_min = res->number_chars_max = res->number_chars;
+      res->range.min = HOST_WIDE_INT_M1U;
+      res->range.max = HOST_WIDE_INT_M1U;
       return false;
     }
 
-  bool warned = res->warned;
-
   /* Compute the number of available bytes in the destination.  There
      must always be at least one byte of space for the terminating
      NUL that's appended after the format string has been processed.  */
-  unsigned HOST_WIDE_INT navail = min_bytes_remaining (info.objsize, *res);
+  // unsigned HOST_WIDE_INT navail = min_bytes_remaining (info.objsize, *res);
+  result_range avail_range = bytes_remaining (info.objsize, *res);
 
-  if (fmtres.range.min < fmtres.range.max)
-    {
-      /* The result is a range (i.e., it's inexact).  */
-      if (!warned)
-	{
-	  if (navail < fmtres.range.min)
-	    {
-	      /* The minimum directive output is longer than there is
-		 room in the destination.  */
-	      if (fmtres.range.min == fmtres.range.max)
-		{
-		  const char* fmtstr
-		    = (info.bounded
-		       ? G_("%<%.*s%> directive output truncated writing "
-			    "%wu bytes into a region of size %wu")
-		       : G_("%<%.*s%> directive writing %wu bytes "
-			    "into a region of size %wu"));
-		  warned = fmtwarn (dirloc, pargrange, NULL,
-				    OPT_Wformat_length_, fmtstr,
-				    dirlen, dirbeg, fmtres.range.min,
-				    navail);
-		}
-	      else if (fmtres.range.max < HOST_WIDE_INT_MAX)
-		{
-		  const char* fmtstr
-		    = (info.bounded
-		       ? G_("%<%.*s%> directive output truncated writing "
-			    "between %wu and %wu bytes into a region of "
-			    "size %wu")
-		       : G_("%<%.*s%> directive writing between %wu and "
-			    "%wu bytes into a region of size %wu"));
-		  warned = fmtwarn (dirloc, pargrange, NULL,
-				    OPT_Wformat_length_, fmtstr,
-				    dirlen, dirbeg,
-				    fmtres.range.min, fmtres.range.max, navail);
-		}
-	      else
-		{
-		  const char* fmtstr
-		    = (info.bounded
-		       ? G_("%<%.*s%> directive output truncated writing "
-			    "%wu or more bytes into a region of size %wu")
-		       : G_("%<%.*s%> directive writing %wu or more bytes "
-			    "into a region of size %wu"));
-		  warned = fmtwarn (dirloc, pargrange, NULL,
-				    OPT_Wformat_length_, fmtstr,
-				    dirlen, dirbeg,
-				    fmtres.range.min, navail);
-		}
-	    }
-	  else if (navail < fmtres.range.max
-		   && (((dir.specifier == 's'
-			 && fmtres.range.max < HOST_WIDE_INT_MAX)
-			/* && (dir.precision || dir.star_precision) */)
-		       || 1 < warn_format_length))
-	    {
-	      /* The maximum directive output is longer than there is
-		 room in the destination and the output length is either
-		 explicitly constrained by the precision (for strings)
- 		 or the warning level is greater than 1.  */
-	      if (fmtres.range.max >= HOST_WIDE_INT_MAX)
-		{
-		  const char* fmtstr
-		    = (info.bounded
-		       ? G_("%<%.*s%> directive output may be truncated "
-			    "writing %wu or more bytes a region of size %wu")
-		       : G_("%<%.*s%> directive writing %wu or more bytes "
-			    "into a region of size %wu"));
-		  warned = fmtwarn (dirloc, pargrange, NULL,
-				    OPT_Wformat_length_, fmtstr,
-				    dirlen, dirbeg,
-				    fmtres.range.min, navail);
-		}
-	      else
-		{
-		  const char* fmtstr
-		    = (info.bounded
-		       ? G_("%<%.*s%> directive output may be truncated "
-			    "writing between %wu and %wu bytes into a region "
-			    "of size %wu")
-		       : G_("%<%.*s%> directive writing between %wu and %wu "
-			    "bytes into a region of size %wu"));
-		  warned = fmtwarn (dirloc, pargrange, NULL,
-				    OPT_Wformat_length_, fmtstr,
-				    dirlen, dirbeg,
-				    fmtres.range.min, fmtres.range.max,
-				    navail);
-		}
-	    }
-	}
+  bool warned = res->warned;
 
-      /* Disable exact length checking but adjust the minimum and maximum.  */
-      res->number_chars = HOST_WIDE_INT_M1U;
-      if (res->number_chars_max < HOST_WIDE_INT_MAX
-	  && fmtres.range.max < HOST_WIDE_INT_MAX)
-	res->number_chars_max += fmtres.range.max;
+  if (!warned)
+    warned = maybe_warn (dirloc, pargrange, info, avail_range,
+			 fmtres.range, dir);
 
-      res->number_chars_min += fmtres.range.min;
-    }
+  /* Bump up the total maximum if it isn't too big.  */
+  if (res->range.max < HOST_WIDE_INT_MAX
+      && fmtres.range.max < HOST_WIDE_INT_MAX)
+    res->range.max += fmtres.range.max;
+
+  /* Raise the total unlikely maximum by the larger of the maximum
+     and the unlikely maximum.  It doesn't matter if the unlikely
+     maximum overflows.  */
+  if (fmtres.range.max < fmtres.range.unlikely)
+    res->range.unlikely += fmtres.range.unlikely;
   else
-    {
-      if (*dir.beg != '%')
-	{
-	  /* For plain character directives (i.e., the format string itself)
-	     but not others, point the caret at the first character that's
-	     past the end of the destination.  */
-	  dirloc.set_caret_index (offset + navail);
-	}
+    res->range.unlikely += fmtres.range.max;
 
-      if (!warned && fmtres.range.min > 0 && navail < fmtres.range.min)
-	{
-	  const char* fmtstr
-	    = (info.bounded
-	       ? (1 < fmtres.range.min
-		  ? G_("%<%.*s%> directive output truncated while writing "
-		       "%wu bytes into a region of size %wu")
-		  : G_("%<%.*s%> directive output truncated while writing "
-		       "%wu byte into a region of size %wu"))
-	       : (1 < fmtres.range.min
-		  ? G_("%<%.*s%> directive writing %wu bytes "
-		       "into a region of size %wu")
-		  : G_("%<%.*s%> directive writing %wu byte "
-		       "into a region of size %wu")));
-
-	  warned = fmtwarn (dirloc, pargrange, NULL,
-			    OPT_Wformat_length_, fmtstr,
-			    dirlen, dirbeg, fmtres.range.min,
-			    navail);
-	}
-      *res += fmtres.range.min;
-    }
+  res->range.min += fmtres.range.min;
+  res->range.likely += fmtres.range.likely;
 
   /* Has the minimum directive output length exceeded the maximum
      of 4095 bytes required to be supported?  */
   bool minunder4k = fmtres.range.min < 4096;
-  if (!minunder4k || fmtres.range.max > 4095)
+  bool maxunder4k = fmtres.range.max < 4096;
+  /* Clear UNDER4K in the overall result if the maximum has exceeded
+     the 4k (this is necessary to avoid the return valuye optimization
+     that may not be safe in the maximum case).  */
+  if (!maxunder4k)
     res->under4k = false;
 
-  if (!warned && 1 < warn_format_length
-      && (!minunder4k || fmtres.range.max > 4095))
+  if (!warned
+      /* Only warn at level 2.  */
+      && 1 < warn_format_length
+      && (!minunder4k
+	  || (!maxunder4k && fmtres.range.max < HOST_WIDE_INT_MAX)))
     {
       /* The directive output may be longer than the maximum required
 	 to be handled by an implementation according to 7.21.6.1, p15
@@ -2125,9 +2430,9 @@  format_directive (const pass_sprintf_length::call_info &info,
 	{
 	  const char *fmtstr
 	    = (minunder4k
-	       ? G_("%<%.*s%> directive output between %qu and %wu "
+	       ? G_("%<%.*s%> directive output between %wu and %wu "
 		    "bytes may exceed minimum required size of 4095")
-	       : G_("%<%.*s%> directive output between %qu and %wu "
+	       : G_("%<%.*s%> directive output between %wu and %wu "
 		    "bytes exceeds minimum required size of 4095"));
 
 	  warned = fmtwarn (dirloc, pargrange, NULL,
@@ -2137,13 +2442,17 @@  format_directive (const pass_sprintf_length::call_info &info,
 	}
     }
 
-  /* Has the minimum directive output length exceeded INT_MAX?  */
-  bool exceedmin = res->number_chars_min > target_int_max ();
+  /* Has the likely and maximum directive output exceeded INT_MAX?  */
+  bool likelyximax = *dirbeg && res->range.likely > target_int_max ();
+  bool maxximax = *dirbeg && res->range.max > target_int_max ();
 
   if (!warned
-      && (exceedmin
+      /* Warn for the likely output size at level 1.  */
+      && (likelyximax
+	  /* But only warn for the maximum at level 2.  */
 	  || (1 < warn_format_length
-	      && res->number_chars_max > target_int_max ())))
+	      && maxximax
+	      && fmtres.range.max < HOST_WIDE_INT_MAX)))
     {
       /* The directive output causes the total length of output
 	 to exceed INT_MAX bytes.  */
@@ -2157,7 +2466,7 @@  format_directive (const pass_sprintf_length::call_info &info,
       else
 	{
 	  const char *fmtstr
-	    = (exceedmin
+	    = (fmtres.range.min > target_int_max ()
 	       ? G_ ("%<%.*s%> directive output between %wu and %wu "
 		     "bytes causes result to exceed %<INT_MAX%>")
 	       : G_ ("%<%.*s%> directive output between %wu and %wu "
@@ -2169,6 +2478,16 @@  format_directive (const pass_sprintf_length::call_info &info,
 	}
     }
 
+  if (warned && fmtres.range.min < fmtres.range.likely
+      && fmtres.range.likely < fmtres.range.max)
+    {
+      inform (info.fmtloc,
+	      (1 == fmtres.range.likely
+	       ? G_("assuming directive output of %wu byte")
+	       : G_("assuming directive output of %wu bytes")),
+	      fmtres.range.likely);
+    }
+
   if (warned && fmtres.argmin)
     {
       if (fmtres.argmin == fmtres.argmax)
@@ -2184,119 +2503,7 @@  format_directive (const pass_sprintf_length::call_info &info,
 
   res->warned |= warned;
 
-  return true;
-}
-
-/* Account for the number of bytes between BEG and END (or between
-   BEG + strlen (BEG) when END is null) in the format string in a call
-   to a formatted output function described by INFO.  Reflect the count
-   in RES and issue warnings as appropriate.  */
-
-static void
-add_bytes (const pass_sprintf_length::call_info &info,
-	   const char *beg, const char *end, format_result *res)
-{
-  if (res->number_chars_min >= HOST_WIDE_INT_MAX)
-    return;
-
-  /* The number of bytes to output is the number of bytes between
-     the end of the last directive and the beginning of the next
-     one if it exists, otherwise the number of characters remaining
-     in the format string plus 1 for the terminating NUL.  */
-  size_t nbytes = end ? end - beg : strlen (beg) + 1;
-
-  /* Return if there are no bytes to add at this time but there are
-     directives remaining in the format string.  */
-  if (!nbytes)
-    return;
-
-  /* Compute the range of available bytes in the destination.  There
-     must always be at least one byte left for the terminating NUL
-     that's appended after the format string has been processed.  */
-  result_range avail_range = bytes_remaining (info.objsize, *res);
-
-  /* If issuing a diagnostic (only when one hasn't already been issued),
-     distinguish between a possible overflow ("may write") and a certain
-     overflow somewhere "past the end."  (Ditto for truncation.)
-     KNOWNRANGE is used to warn even at level 1 about possibly writing
-     past the end or truncation due to strings of unknown lengths that
-     are bounded by the arrays they are known to refer to.  */
-  if (!res->warned
-      && (avail_range.max < nbytes
-	  || ((res->knownrange || 1 < warn_format_length)
-	      && avail_range.min < nbytes)))
-    {
-      /* Set NAVAIL to the number of available bytes used to decide
-	 whether or not to issue a warning below.  The exact kind of
-	 warning will depend on AVAIL_RANGE.  */
-      unsigned HOST_WIDE_INT navail = avail_range.max;
-      if (nbytes <= navail && avail_range.min < HOST_WIDE_INT_MAX
-	  && (res->knownrange || 1 < warn_format_length))
-	navail = avail_range.min;
-
-      /* Compute the offset of the first format character that is beyond
-	 the end of the destination region and the length of the rest of
-	 the format string from that point on.  */
-      unsigned HOST_WIDE_INT off
-	= (unsigned HOST_WIDE_INT)(beg - info.fmtstr) + navail;
-
-      size_t len = strlen (info.fmtstr + off);
-
-      /* Create a location that underscores the substring of the format
-	 string that is or may be written past the end (or is or may be
-	 truncated), pointing the caret at the first character of the
-	 substring.  */
-      substring_loc loc
-	(info.fmtloc, TREE_TYPE (info.format), off, len ? off : 0,
-	 off + len - !!len);
-
-      /* Is the output of the last directive the result of the argument
-	 being within a range whose lower bound would fit in the buffer
-	 but the upper bound would not?  If so, use the word "may" to
-	 indicate that the overflow/truncation may (but need not) happen.  */
-      bool boundrange
-	= (res->number_chars_min < res->number_chars_max
-	   && res->number_chars_min + nbytes <= info.objsize);
-
-      if (!end && ((nbytes - navail) == 1 || boundrange))
-	{
-	  /* There is room for the rest of the format string but none
-	     for the terminating nul.  */
-	  const char *text
-	    = (info.bounded   // Snprintf and the like.
-	       ? (boundrange
-		  ? G_("output may be truncated before the last format character"
-		       : "output truncated before the last format character"))
-	       : (boundrange
-		  ? G_("may write a terminating nul past the end "
-		       "of the destination")
-		  : G_("writing a terminating nul past the end "
-		       "of the destination")));
-
-	  res->warned = fmtwarn (loc, NULL, NULL, OPT_Wformat_length_, text);
-	}
-      else
-	{
-	  /* There isn't enough room for 1 or more characters that remain
-	     to copy from the format string.  */
-	  const char *text
-	    = (info.bounded   // Snprintf and the like.
-	       ? (boundrange
-		  ? G_("output may be truncated at or before format character "
-		       "%qc at offset %wu")
-		  : G_("output truncated at format character %qc at offset %wu"))
-	       : (res->number_chars >= HOST_WIDE_INT_MAX
-		  ? G_("may write format character %#qc at offset %wu past "
-		       "the end of the destination")
-		  : G_("writing format character %#qc at offset %wu past "
-		       "the end of the destination")));
-
-	  res->warned = fmtwarn (loc, NULL, NULL, OPT_Wformat_length_,
-				 text, info.fmtstr[off], off);
-	}
-    }
-
-  if (res->warned && !end && info.objsize < HOST_WIDE_INT_MAX)
+  if (!dir.beg[0] && res->warned && info.objsize < HOST_WIDE_INT_MAX)
     {
       /* If a warning has been issued for buffer overflow or truncation
 	 (but not otherwise) help the user figure out how big a buffer
@@ -2304,80 +2511,46 @@  add_bytes (const pass_sprintf_length::call_info &info,
 
       location_t callloc = gimple_location (info.callstmt);
 
-      unsigned HOST_WIDE_INT min = res->number_chars_min;
-      unsigned HOST_WIDE_INT max = res->number_chars_max;
-      unsigned HOST_WIDE_INT exact
-	= (res->number_chars < HOST_WIDE_INT_MAX
-	   ? res->number_chars : res->number_chars_min);
+      unsigned HOST_WIDE_INT min = res->range.min;
+      unsigned HOST_WIDE_INT max = res->range.max;
 
-      if (min < max && max < HOST_WIDE_INT_MAX)
-	inform (callloc,
-		"%qE output between %wu and %wu bytes into "
-		"a destination of size %wu",
-		info.func, min + nbytes, max + nbytes, info.objsize);
-      else
+      if (min == max)
 	inform (callloc,
-		(nbytes + exact == 1
+		(min == 1
 		 ? G_("%qE output %wu byte into a destination of size %wu")
 		 : G_("%qE output %wu bytes into a destination of size %wu")),
-		info.func, nbytes + exact, info.objsize);
+		info.func, min, info.objsize);
+      else if (max < HOST_WIDE_INT_MAX)
+	inform (callloc,
+		"%qE output between %wu and %wu bytes into a destination "
+		"of size %wu",
+		info.func, min, max, info.objsize);
+      else if (min < res->range.likely && res->range.likely < max)
+	inform (callloc,
+		"%qE output %wu or more bytes (assuming %wu) into a destination "
+		"of size %wu",
+		info.func, min, res->range.likely, info.objsize);
+      else
+	inform (callloc,
+		"%qE output %wu or more bytes into a destination of size %wu",
+		info.func, min, info.objsize);
     }
 
-  /* Add the number of bytes and then check for INT_MAX overflow.  */
-  *res += nbytes;
-
-  /* Has the minimum output length minus the terminating nul exceeded
-     INT_MAX?  */
-  bool exceedmin = (res->number_chars_min - !end) > target_int_max ();
-
-  if (!res->warned
-      && (exceedmin
-	  || (1 < warn_format_length
-	      && (res->number_chars_max - !end) > target_int_max ())))
+  if (dump_file && *dir.beg)
     {
-      /* The function's output exceeds INT_MAX bytes.  */
-
-      /* Set NAVAIL to the number of available bytes used to decide
-	 whether or not to issue a warning below.  The exact kind of
-	 warning will depend on AVAIL_RANGE.  */
-      unsigned HOST_WIDE_INT navail = avail_range.max;
-      if (nbytes <= navail && avail_range.min < HOST_WIDE_INT_MAX
-	  && (res->bounded || 1 < warn_format_length))
-	navail = avail_range.min;
-
-      /* Compute the offset of the first format character that is beyond
-	 the end of the destination region and the length of the rest of
-	 the format string from that point on.  */
-      unsigned HOST_WIDE_INT off = (unsigned HOST_WIDE_INT)(beg - info.fmtstr);
-      if (navail < HOST_WIDE_INT_MAX)
-	off += navail;
-
-      size_t len = strlen (info.fmtstr + off);
-
-      substring_loc loc
-	(info.fmtloc, TREE_TYPE (info.format), off - !len, len ? off : 0,
-	 off + len - !!len);
-
-      if (res->number_chars_min == res->number_chars_max)
-	res->warned = fmtwarn (loc, NULL, NULL,
-			       OPT_Wformat_length_,
-			       "output of %wu bytes causes "
-			       "result to exceed %<INT_MAX%>",
-			       res->number_chars_min - !end);
-      else
-	{
-	  const char *text
-	    = (exceedmin
-	       ? G_ ("output between %wu and %wu bytes causes "
-		     "result to exceed %<INT_MAX%>")
-	       : G_ ("output between %wu and %wu bytes may cause "
-		     "result to exceed %<INT_MAX%>"));
-	  res->warned = fmtwarn (loc, NULL, NULL, OPT_Wformat_length_,
-				 text,
-				 res->number_chars_min - !end,
-				 res->number_chars_max - !end);
-	}
+      fprintf (dump_file, "    Result: %lli, %lli, %lli, %lli "
+	       "(%lli, %lli, %lli, %lli)\n",
+	       (long long)fmtres.range.min,
+	       (long long)fmtres.range.likely,
+	       (long long)fmtres.range.max,
+	       (long long)fmtres.range.unlikely,
+	       (long long)res->range.min,
+	       (long long)res->range.likely,
+	       (long long)res->range.max,
+	       (long long)res->range.unlikely);
     }
+
+  return true;
 }
 
 #pragma GCC diagnostic pop
@@ -2390,24 +2563,34 @@  parse_directive (const pass_sprintf_length::call_info &info,
   const char *pcnt = strchr (str, '%');
   dir.beg = str;
 
-  if (!pcnt)
-    {
-      dir.len = strlen (str);
-      dir.fmtfunc = format_plain;
-      return dir.len;
-    }
-
-  if (size_t len = pcnt - str)
+  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 = format_plain;
-      return len;
+
+      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;
+  HOST_WIDE_INT width = -1;
+  HOST_WIDE_INT precision = -1;
+
+  tree star_width = NULL_TREE;
+  tree star_precision = NULL_TREE;
 
   if (ISDIGIT (*pf))
     {
@@ -2416,8 +2599,7 @@  parse_directive (const pass_sprintf_length::call_info &info,
 	 width and sort it out later after the next character has
 	 been seen.  */
       char *end;
-      dir.width = strtol (pf, &end, 10);
-      dir.have_width = true;
+      width = strtol (pf, &end, 10);
       pf = end;
     }
   else if ('*' == *pf)
@@ -2427,7 +2609,7 @@  parse_directive (const pass_sprintf_length::call_info &info,
       if (gimple_call_num_args (info.callstmt) <= *argno)
 	return 0;
 
-      dir.star_width = gimple_call_arg (info.callstmt, (*argno)++);
+      star_width = gimple_call_arg (info.callstmt, (*argno)++);
       ++pf;
     }
 
@@ -2435,11 +2617,11 @@  parse_directive (const pass_sprintf_length::call_info &info,
     {
       /* Handle the POSIX dollar sign which references the 1-based
 	 positional argument number.  */
-      if (dir.have_width)
-	dollar = dir.width + info.argidx;
-      else if (dir.star_width
-	       && TREE_CODE (dir.star_width) == INTEGER_CST)
-	dollar = dir.width + tree_to_shwi (dir.star_width);
+      if (width != -1)
+	dollar = width + info.argidx;
+      else if (star_width
+	       && TREE_CODE (star_width) == INTEGER_CST)
+	dollar = width + tree_to_shwi (star_width);
 
       /* Bail when the numbered argument is out of range (it will
 	 have already been diagnosed by -Wformat).  */
@@ -2450,21 +2632,21 @@  parse_directive (const pass_sprintf_length::call_info &info,
 
       --dollar;
 
-      dir.star_width = NULL_TREE;
-      dir.have_width = false;
+      star_width = NULL_TREE;
+      width = -1;
       ++pf;
     }
 
-  if (dollar || !dir.star_width)
+  if (dollar || !star_width)
     {
-      if (dir.have_width)
+      if (width != -1)
 	{
-	  if (dir.width == 0)
+	  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.  */
-	      dir.have_width = false;
+	      width = -1;
 	      dir.set_flag ('0');
 	    }
 	  else if (!dollar)
@@ -2497,13 +2679,12 @@  parse_directive (const pass_sprintf_length::call_info &info,
       if (ISDIGIT (*pf))
 	{
 	  char *end;
-	  dir.width = strtol (pf, &end, 10);
-	  dir.have_width = true;
+	  width = strtol (pf, &end, 10);
 	  pf = end;
 	}
       else if ('*' == *pf)
 	{
-	  dir.star_width = gimple_call_arg (info.callstmt, (*argno)++);
+	  star_width = gimple_call_arg (info.callstmt, (*argno)++);
 	  ++pf;
 	}
       else if ('\'' == *pf)
@@ -2525,21 +2706,19 @@  parse_directive (const pass_sprintf_length::call_info &info,
       if (ISDIGIT (*pf))
 	{
 	  char *end;
-	  dir.precision = strtol (pf, &end, 10);
-	  dir.have_precision = true;
+	  precision = strtol (pf, &end, 10);
 	  pf = end;
 	}
       else if ('*' == *pf)
 	{
-	  dir.star_precision = gimple_call_arg (info.callstmt, (*argno)++);
+	  star_precision = gimple_call_arg (info.callstmt, (*argno)++);
 	  ++pf;
 	}
       else
 	{
 	  /* The decimal precision or the asterisk are optional.
 	     When neither is dirified it's taken to be zero.  */
-	  dir.precision = 0;
-	  dir.have_precision = true;
+	  precision = 0;
 	}
     }
 
@@ -2594,7 +2773,7 @@  parse_directive (const pass_sprintf_length::call_info &info,
 	 undefined prevent the result from being folded.  */
     case '\0':
       --pf;
-      res->bounded = false;
+      res->range.min = res->range.max = HOST_WIDE_INT_M1U;
       /* FALLTHRU */
     case '%':
       dir.fmtfunc = format_percent;
@@ -2632,6 +2811,9 @@  parse_directive (const pass_sprintf_length::call_info &info,
       break;
 
     case 'c':
+      dir.fmtfunc = format_character;
+      break;
+
     case 'S':
     case 's':
       dir.fmtfunc = format_string;
@@ -2644,6 +2826,26 @@  parse_directive (const pass_sprintf_length::call_info &info,
 
   dir.specifier = *pf++;
 
+  if (star_width)
+    {
+      if (TREE_CODE (TREE_TYPE (star_width)) != INTEGER_TYPE)
+	return 0;
+
+      dir.set_width (star_width);
+    }
+  else
+    dir.set_width (width);
+
+  if (star_precision)
+    {
+      if (TREE_CODE (TREE_TYPE (star_precision)) != INTEGER_TYPE)
+	return 0;
+
+      dir.set_precision (star_precision);
+    }
+  else
+    dir.set_precision (precision);
+
   /* 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
@@ -2654,6 +2856,32 @@  parse_directive (const pass_sprintf_length::call_info &info,
 
   /* Return the length of the format directive.  */
   dir.len = pf - pcnt;
+
+  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)
+	{
+	  if (dir.width[0] == dir.width[1])
+	    fprintf (dump_file, ", width = %lli", (long long)dir.width[0]);
+	  else
+	    fprintf (dump_file, ", width in range [%lli, %lli]",
+		     (long long)dir.width[0], (long long)dir.width[1]);
+	}
+
+      if (star_precision)
+	{
+	  if (dir.prec[0] == dir.prec[1])
+	    fprintf (dump_file, ", precision = %lli", (long long)dir.prec[0]);
+	  else
+	    fprintf (dump_file, ", precision in range [%lli, %lli]",
+		     (long long)dir.prec[0], (long long)dir.prec[1]);
+	}
+      fputc ('\n', dump_file);
+    }
+
   return dir.len;
 }
 
@@ -2668,40 +2896,56 @@  bool
 pass_sprintf_length::compute_format_length (const call_info &info,
 					    format_result *res)
 {
+  if (dump_file)
+    {
+      location_t callloc = gimple_location (info.callstmt);
+      fprintf (dump_file, "%s:%i: ",
+	       LOCATION_FILE (callloc), LOCATION_LINE (callloc));
+      print_generic_expr (dump_file, info.func, dump_flags);
+
+      fprintf (dump_file, ": objsize = %llu, fmtstr = \"%s\"\n",
+	       (unsigned long long)info.objsize, info.fmtstr);
+    }
+
   /* Reset exact, minimum, and maximum character counters.  */
-  res->number_chars = res->number_chars_min = res->number_chars_max = 0;
+  res->range.min = res->range.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;
 
-  const char *pf;
-  for (pf = info.fmtstr; *pf; )
+  for (const char *pf = info.fmtstr; ; ++dirno)
     {
       directive dir = directive ();
+      dir.dirno = dirno;
 
       size_t n = parse_directive (info, dir, res, pf, &argno);
-      if (!n)
-	return false;
-
-      pf += n;
 
+      /* Return failure if the format function fails.  */
       if (!format_directive (info, res, dir))
 	return false;
-    }
 
-  add_bytes (info, pf, 0, res);
+      /* 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';
+
+      pf += n;
+    }
 
-  /* Complete format string was processed (with or without warnings).  */
+  /* The complete format string was processed (with or without warnings).  */
   return true;
 }
 
@@ -2736,6 +2980,27 @@  try_substitute_return_value (gimple_stmt_iterator *gsi,
 			     const format_result &res)
 {
   tree lhs = gimple_get_lhs (info.callstmt);
+  if (!lhs)
+    return;
+
+  unsigned HOST_WIDE_INT minretval = res.range.min;
+
+  /* The maximum return value is in most cases bounded by RES.RANGE.MAX
+     but in cases involving multibyte characters could be as large as
+     RES.RANGE.UNLIKELY.  */
+  unsigned HOST_WIDE_INT maxretval
+    = res.range.unlikely < res.range.max ? res.range.max : res.range.unlikely;
+
+  /* Adjust the number of bytes which includes the terminating nul
+     to reflect the return value of the function which does not.
+     Because the valid range of the function is [INT_MIN, INT_MAX],
+     a valid range before the adjustment below is [0, INT_MAX + 1]
+     (the functions only return negative values on error or undefined
+     behavior).  */
+  if (minretval <= target_int_max () + 1)
+    --minretval;
+  if (maxretval <= target_int_max () + 1)
+    --maxretval;
 
   /* Avoid the return value optimization when the behavior of the call
      is undefined either because any directive may have produced 4K or
@@ -2743,18 +3008,18 @@  try_substitute_return_value (gimple_stmt_iterator *gsi,
      the output overflows the destination object (but leave it enabled
      when the function is bounded because then the behavior is well-
      defined).  */
-  if (lhs && res.bounded && res.under4k
-      && (info.bounded || res.number_chars <= info.objsize)
-      && res.number_chars - 1 <= target_int_max ())
+  if (res.under4k
+      && minretval == maxretval
+      && (info.bounded || minretval < info.objsize)
+      && minretval <= target_int_max ())
     {
-      tree cst = build_int_cst (integer_type_node, res.number_chars - 1);
+      tree cst = build_int_cst (integer_type_node, minretval);
 
       if (info.nowrite)
 	{
 	  /* Replace the call to the bounded function with a zero size
 	     (e.g., snprintf(0, 0, "%i", 123) with the constant result
-	     of the function minus 1 for the terminating NUL which
-	     the function's  return value does not include.  */
+	     of the function.  */
 	  if (!update_call_from_tree (gsi, cst))
 	    gimplify_and_update_call_from_tree (gsi, cst);
 	  gimple *callstmt = gsi_stmt (*gsi);
@@ -2763,8 +3028,7 @@  try_substitute_return_value (gimple_stmt_iterator *gsi,
       else
 	{
 	  /* Replace the left-hand side of the call with the constant
-	     result of the formatted function minus 1 for the terminating
-	     NUL which the function's return value does not include.  */
+	     result of the formatted function.  */
 	  gimple_call_set_lhs (info.callstmt, NULL_TREE);
 	  gimple *g = gimple_build_assign (lhs, cst);
 	  gsi_insert_after (gsi, g, GSI_NEW_STMT);
@@ -2773,72 +3037,57 @@  try_substitute_return_value (gimple_stmt_iterator *gsi,
 
       if (dump_file)
 	{
-	  location_t callloc = gimple_location (info.callstmt);
-	  fprintf (dump_file, "On line %i substituting ",
-		   LOCATION_LINE (callloc));
+	  fprintf (dump_file, "  Substituting %llu",
+		   (unsigned long long)info.objsize);
 	  print_generic_expr (dump_file, cst, dump_flags);
-	  fprintf (dump_file, " for ");
-	  print_generic_expr (dump_file, info.func, dump_flags);
-	  fprintf (dump_file, " %s (output %s).\n",
-		   info.nowrite ? "call" : "return value",
-		   res.constant ? "constant" : "variable");
+	  fprintf (dump_file, " for %s.\n",
+		   info.nowrite ? "statement" : "return value");
 	}
     }
   else
     {
-      unsigned HOST_WIDE_INT maxbytes;
+      bool setrange = false;
 
-      if (lhs
-	  && res.bounded
-	  && ((maxbytes = res.number_chars - 1) <= target_int_max ()
-	      || (res.number_chars_min - 1 <= target_int_max ()
-		  && (maxbytes = res.number_chars_max - 1) <= target_int_max ()))
-	  && (info.bounded || maxbytes < info.objsize))
+      if ((info.bounded || maxretval < info.objsize)
+	  && res.under4k
+	  && (minretval < target_int_max ()
+	      && maxretval < target_int_max ()))
 	{
 	  /* If the result is in a valid range bounded by the size of
 	     the destination set it so that it can be used for subsequent
 	     optimizations.  */
 	  int prec = TYPE_PRECISION (integer_type_node);
 
-	  if (res.number_chars < target_int_max () && res.under4k)
-	    {
-	      wide_int num = wi::shwi (res.number_chars - 1, prec);
-	      set_range_info (lhs, VR_RANGE, num, num);
-	    }
-	  else if (res.number_chars_min < target_int_max ()
-		   && res.number_chars_max < target_int_max ())
-	    {
-	      wide_int min = wi::shwi (res.under4k ? res.number_chars_min - 1
-				       : target_int_min (), prec);
-	      wide_int max = wi::shwi (res.number_chars_max - 1, prec);
-	      set_range_info (lhs, VR_RANGE, min, max);
-	    }
+	  wide_int min = wi::shwi (minretval, prec);
+	  wide_int max = wi::shwi (maxretval, prec);
+	  set_range_info (lhs, VR_RANGE, min, max);
+
+	  setrange = true;
 	}
 
       if (dump_file)
 	{
 	  const char *inbounds
-	    = (res.number_chars_min <= info.objsize
-	       ? (res.number_chars_max <= info.objsize
+	    = (minretval <= info.objsize
+	       ? (maxretval <= info.objsize
 		  ? "in" : "potentially out-of")
 	       : "out-of");
 
-	  location_t callloc = gimple_location (info.callstmt);
-	  fprintf (dump_file, "On line %i ", LOCATION_LINE (callloc));
-	  print_generic_expr (dump_file, info.func, dump_flags);
-
-	  const char *ign = lhs ? "" : " ignored";
-	  if (res.number_chars >= HOST_WIDE_INT_MAX)
+	  const char *what = setrange ? "Setting" : "Discarding";
+	  if (minretval != maxretval)
 	    fprintf (dump_file,
-		     " %s-bounds return value in range [%lu, %lu]%s.\n",
-		     inbounds,
-		     (unsigned long)res.number_chars_min,
-		     (unsigned long)res.number_chars_max, ign);
+		     "  %s %s-bounds return value range [%llu, %llu].\n",
+		     what, inbounds,
+		     (unsigned long long)minretval,
+		     (unsigned long long)maxretval);
 	  else
-	    fprintf (dump_file, " %s-bounds return value %lu%s.\n",
-		     inbounds, (unsigned long)res.number_chars, ign);
+	    fprintf (dump_file, "  %s %s-bounds return value %llu.\n",
+		     what, inbounds, (unsigned long long)minretval);
 	}
     }
+
+  if (dump_file)
+    fputc ('\n', dump_file);
 }
 
 /* Determine if a GIMPLE CALL is to one of the sprintf-like built-in
diff --git a/gcc/substring-locations.c b/gcc/substring-locations.c
index 60bf1b0..23b94fc 100644
--- a/gcc/substring-locations.c
+++ b/gcc/substring-locations.c
@@ -191,5 +191,14 @@  const char *
 substring_loc::get_location (location_t *out_loc) const
 {
   gcc_assert (out_loc);
+  if (m_caret_idx < 0)
+    return "m_caret_idx < 0";
+
+  if (m_start_idx < 0)
+    return "m_start_idx < 0";
+
+  if (m_end_idx < 0)
+    return "m_end_idx < 0";
+
   return lang_hooks.get_substring_location (*this, out_loc);
 }
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-2.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-2.c
index 260f4fc..b873a0c 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-2.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-2.c
@@ -59,15 +59,15 @@  void must_not_eliminate (void);
 
 typedef __SIZE_TYPE__ size_t;
 
-extern int i;
-extern unsigned u;
-extern long li;
-extern unsigned long lu;
-extern size_t sz;
-extern char *str;
+volatile int i;
+volatile unsigned u;
+volatile long li;
+volatile unsigned long lu;
+volatile size_t sz;
+volatile char *str;
 
-extern double d;
-extern long double ld;
+volatile double d;
+volatile long double ld;
 
 /* Verify that overflowing the destination object disables the return
    value optimization.  */
@@ -280,7 +280,8 @@  RNG (0,  6,   8, "%s%ls", "1", L"2");
 
 */
 
-/*  Only conditional calls to abort should be made (with any probability):
+/*  Only conditional calls to must_not_eliminate must be made (with
+    any probability):
     { dg-final { scan-tree-dump-times "> \\\[\[0-9.\]+%\\\]:\n *must_not_eliminate" 124 "optimized" { target { ilp32 || lp64 } } } }
     { dg-final { scan-tree-dump-times "> \\\[\[0-9.\]+%\\\]:\n *must_not_eliminate" 93 "optimized" { target { { ! ilp32 } && { ! lp64 } } } } }
     No unconditional calls to abort should be made:
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-5.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-5.c
index c4489ac..abffcae 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-5.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-5.c
@@ -135,7 +135,6 @@  void test_arg_string (const char *s)
 
 void test_arg_multiarg (int i, double d)
 {
-  EQL (16, "%i %f %s", 123, 3.14, "abc");
   EQL (16, "%12i %s", i, "abc");
   EQL (16, "%*i %s", 12, i, "abc");
 }
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c
index aca11e5..e34f0bc 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c
@@ -121,19 +121,21 @@  void test_sprintf_flexarray (void *p, int i)
   struct S
   {
     int n;
-    char a [];
+    char a[];
   } *s = p;
 
-  __builtin_sprintf (s->a, "%c",       'x');
+  FUNC (sprintf)(s->a, "%c",       'x');
 
-  __builtin_sprintf (s->a, "%s",       "");
-  __builtin_sprintf (s->a, "%s",       "abc");
-  __builtin_sprintf (s->a, "abc%sghi", "def");
+  FUNC (sprintf)(s->a, "%-s",      "");
+  FUNC (sprintf)(s->a, "%-s",      "abc");
+  FUNC (sprintf)(s->a, "abc%sghi", "def");
 
-  __builtin_sprintf (s->a, "%i",       1234);
+  FUNC (sprintf)(s->a, "%i",       1234);
 
-  __builtin_sprintf (buffer (1), "%s",  s->a);
-  __builtin_sprintf (buffer (1), "%s",  s [i].a);
+  FUNC (sprintf)(buffer (1), "%-s", s->a);      /* { dg-warning "may write a terminating nul past the end" } */
+  FUNC (sprintf)(buffer (1), "%-s", s [i].a);   /* { dg-warning "may write a terminating nul past the end" } */
+  FUNC (sprintf)(buffer (2), "%-s", s->a);
+  FUNC (sprintf)(buffer (2), "%-s", s [i].a);
 }
 
 /* Same as above but for zero-length arrays.  */
@@ -146,16 +148,16 @@  void test_sprintf_zero_length_array (void *p, int i)
     char a [0];
   } *s = p;
 
-  __builtin_sprintf (s->a, "%c",       'x');
+  FUNC (sprintf)(s->a, "%c",       'x');
 
-  __builtin_sprintf (s->a, "%s",       "");
-  __builtin_sprintf (s->a, "%s",       "abc");
-  __builtin_sprintf (s->a, "abc%sghi", "def");
+  FUNC (sprintf)(s->a, "%s",       "");
+  FUNC (sprintf)(s->a, "%s",       "abc");
+  FUNC (sprintf)(s->a, "abc%sghi", "def");
 
-  __builtin_sprintf (s->a, "%i",       1234);
+  FUNC (sprintf)(s->a, "%i",       1234);
 
-  __builtin_sprintf (buffer (1), "%s",  s->a);
-  __builtin_sprintf (buffer (1), "%s",  s [i].a);
+  FUNC (sprintf)(buffer (1), "%s",  s->a);
+  FUNC (sprintf)(buffer (1), "%s",  s [i].a);
 }
 
 #undef T
@@ -298,6 +300,7 @@  void test_sprintf_chk_s_const (void)
 
   T (2, "_%s",   "");
   T (2, "%%%s",  "");
+  T (2, "%%%%%s",  "");         /* { dg-warning "nul past the end" } */
   T (2, "%s%%",  "");
   T (2, "_%s",   "1");          /* { dg-warning "nul past the end" } */
   T (2, "%%%s",  "1");          /* { dg-warning "nul past the end" } */
@@ -329,7 +332,10 @@  void test_sprintf_chk_s_const (void)
   T (1, "%*ls",  0, L"\0");
   T (1, "%*ls",  1, L"");       /* { dg-warning "nul past the end" } */
 
-  T (1, "%ls",      L"1");      /* { dg-warning "nul past the end" } */
+  /* A wide character string need not convert into any bytes (although
+     individual ASCII characters are assumed to convert into 1 bt %lc
+     so this could be made smarter.  */
+  T (1, "%ls",      L"1");      /* { dg-warning "directive writing up to 6 bytes into a region of size 1" } */
   T (1, "%.0ls",    L"1");
   T (2, "%.0ls",    L"1");
   T (2, "%.1ls",    L"1");
@@ -345,9 +351,9 @@  void test_sprintf_chk_s_const (void)
   T (3, "%.0ls",    L"1");
   T (3, "%.1ls",    L"1");
   T (3, "%.2ls",    L"1");
-  T (3, "%ls",      L"12");
+  T (3, "%ls",      L"12");     /* { dg-warning "directive writing up to 12 bytes" } */
 
-  T (3, "%ls",      L"123");    /* { dg-warning "nul past the end" } */
+  T (3, "%ls",      L"123");    /* { dg-warning "directive writing up to 18 bytes" } */
   T (3, "%.0ls",    L"123");
   T (3, "%.1ls",    L"123");
   T (3, "%.2ls",    L"123");
@@ -954,16 +960,20 @@  void test_sprintf_chk_a_const (void)
 
   /* The least number of bytes on output is 6 for "0x0p+0".  When precision
      is missing the number of digits after the decimal point isn't fully
-     specified by C (it seems like a defect).  */
-  T (0, "%a",   0.0);         /* { dg-warning "into a region" } */
-  T (0, "%la",  0.0);         /* { dg-warning "into a region" } */
-  T (1, "%a",   0.0);         /* { dg-warning "into a region" } */
-  T (2, "%a",   0.0);         /* { dg-warning "into a region" } */
-  T (3, "%a",   0.0);         /* { dg-warning "into a region" } */
-  T (4, "%a",   0.0);         /* { dg-warning "into a region" } */
-  T (5, "%a",   0.0);         /* { dg-warning "into a region" } */
-  T (6, "%a",   0.0);         /* { dg-warning "writing a terminating nul" } */
-  T (7, "%a",   0.0);
+     specified by C (a defect).  Two sets of implementations are known to
+     exist: those that trim trailing zeros (e.g., Glibc) and those that
+     pad output with trailing zeros so that all floating point numbers
+     result in the same number of bytes on output (e.g., Solaris).  */
+  T ( 0, "%a",   0.0);         /* { dg-warning "writing between 6 and 20 bytes" } */
+  T ( 0, "%la",  0.0);         /* { dg-warning "between 6 and 20 bytes" } */
+  T ( 1, "%a",   0.0);         /* { dg-warning "between 6 and 20 bytes" } */
+  T ( 2, "%a",   0.0);         /* { dg-warning "between 6 and 20 bytes" } */
+  T ( 3, "%a",   0.0);         /* { dg-warning "between 6 and 20 bytes" } */
+  T ( 4, "%a",   0.0);         /* { dg-warning "between 6 and 20 bytes" } */
+  T ( 5, "%a",   0.0);         /* { dg-warning "between 6 and 20 bytes" } */
+  T ( 6, "%a",   0.0);         /* { dg-warning "between 6 and 20 bytes" } */
+  T (19, "%a",   0.0);         /* { dg-warning "between 6 and 20 bytes" } */
+  T (20, "%a",   0.0);         /* { dg-warning "may write a terminating nul" } */
 
   T (0, "%.a",    0.0);       /* { dg-warning "into a region" } */
   T (0, "%.0a",   0.0);       /* { dg-warning "into a region" } */
@@ -1020,24 +1030,21 @@  void test_sprintf_chk_e_const (void)
   T ( 6, "%.e",  1.0);
   T ( 6, "%.0e", 1.0);
 
-  /* The actual output of the following directives depends on the rounding
-     mode.  Verify that the warning correctly reflects that.  At level 1,
-     since the minimum number of bytes output by the directive fits the
-     space the directive itself isn't diagnosed but the terminating nul
-     is.  The directive is diagnosed at level 2.  */
-  T (12, "%e",  9.999999e+99);  /* { dg-warning "terminating nul" } */
-  T (12, "%e",  9.9999994e+99); /* { dg-warning "terminating nul" } */
-  T (12, "%e",  9.9999995e+99); /* { dg-warning "terminating nul" } */
-  T (12, "%e",  9.9999996e+99); /* { dg-warning "terminating nul" } */
-  T (12, "%e",  9.9999997e+99); /* { dg-warning "terminating nul" } */
-  T (12, "%e",  9.9999998e+99); /* { dg-warning "terminating nul" } */
-
-  T (12, "%Le", 9.9999994e+99L);/* { dg-warning "terminating nul" } */
-  T (12, "%Le", 9.9999995e+99L);/* { dg-warning "terminating nul" } */
-  T (12, "%Le", 9.9999996e+99L);/* { dg-warning "terminating nul" } */
-  T (12, "%Le", 9.9999997e+99L);/* { dg-warning "terminating nul" } */
-  T (12, "%Le", 9.9999998e+99L);/* { dg-warning "terminating nul" } */
-  T (12, "%Le", 9.9999999e+99L);/* { dg-warning "terminating nul" } */
+  /* The output of the following directives depends on the rounding
+     mode.  */
+  T (12, "%e",  9.999999e+99);  /* { dg-warning "between 12 and 13" } */
+  T (12, "%e",  9.9999994e+99); /* { dg-warning "between 12 and 13" } */
+  T (12, "%e",  9.9999995e+99); /* { dg-warning "between 12 and 13" } */
+  T (12, "%e",  9.9999996e+99); /* { dg-warning "between 12 and 13" } */
+  T (12, "%e",  9.9999997e+99); /* { dg-warning "between 12 and 13" } */
+  T (12, "%e",  9.9999998e+99); /* { dg-warning "between 12 and 13" } */
+
+  T (12, "%Le", 9.9999994e+99L);/* { dg-warning "between 12 and 13" } */
+  T (12, "%Le", 9.9999995e+99L);/* { dg-warning "between 12 and 13" } */
+  T (12, "%Le", 9.9999996e+99L);/* { dg-warning "between 12 and 13" } */
+  T (12, "%Le", 9.9999997e+99L);/* { dg-warning "between 12 and 13" } */
+  T (12, "%Le", 9.9999998e+99L);/* { dg-warning "between 12 and 13" } */
+  T (12, "%Le", 9.9999999e+99L);/* { dg-warning "between 12 and 13" } */
 }
 
 /* At -Wformat-length level 1 unknown numbers are assumed to have
@@ -1047,21 +1054,32 @@  void test_sprintf_chk_e_const (void)
 void test_sprintf_chk_s_nonconst (int w, int p, const char *s)
 {
   T (-1, "%s",   s);
-  T ( 0, "%s",   s);            /* { dg-warning "nul past the end" } */
-  T ( 1, "%s",   s);
+  T ( 0, "%s",   s);            /* { dg-warning "writing likely 1 or more bytes" } */
+  T ( 1, "%s",   s);            /* { dg-warning "may write a terminating nul" } */
+  T (-1, "%.0s", s);
   T ( 1, "%.0s", s);
-  T ( 1, "%.1s", s);            /* { dg-warning "nul past the end" } */
+  T (-1, "%.1s", s);
+  T ( 1, "%.1s", s);            /* { dg-warning "may write a terminating nul past the end" } */
+  T (-1, "%.2s", s);
+  T ( 1, "%.2s", s);            /* { dg-warning "directive writing up to 2 bytes" } */
+  T ( 2, "%.2s", s);            /* { dg-warning "may write a terminating nul" } */
+  T ( 3, "%.2s", s);
 
   /* The string argument is constant but the width and/or precision
      is not.  */
-  T ( 1, "%*s",  w, "");
-  T ( 1, "%*s",  w, "1");       /* { dg-warning "nul past the end" } */
+  T (-1, "%*s",  w, "");
+  T ( 1, "%*s",  w, "");        /* { dg-warning "may write a terminating nul past the end" } */
+  T (-1, "%*s",  w, "1");
+  T ( 1, "%*s",  w, "1");       /* { dg-warning "writing a terminating nul past the end" } */
+  T (-1, "%.*s", p, "");
   T ( 1, "%.*s", p, "");
+  T (-1, "%.*s", p, "1");
   T ( 1, "%.*s", p, "1");       /* { dg-warning "may write a terminating nul" } */
-  T ( 1, "%.*s", w, "123");     /* { dg-warning "writing between 0 and 3 bytes into a region of size 1" } */
+  T ( 1, "%.*s", w, "123");     /* { dg-warning "writing up to 3 bytes into a region of size 1" } */
 
-  T ( 1, "%*s", w, "123");      /* { dg-warning "writing 3 or more bytes into a region of size 1" } */
-  T ( 2, "%*s", w, "123");      /* { dg-warning "writing 3 or more bytes into a region of size 2" } */
+  /* Either of the messages below is acceptable.  */
+  T ( 1, "%*s", w, "123");      /* { dg-warning "writing 3 or more bytes into a region of size 1|writing between 3 and 2147483648 bytes" } */
+  T ( 2, "%*s", w, "123");      /* { dg-warning "writing 3 or more bytes into a region of size 2|writing between 3 and 2147483648 bytes" } */
   T ( 3, "%*s", w, "123");      /* { dg-warning "writing a terminating nul past the end" } */
   T ( 4, "%*s", w, "123");
 
@@ -1071,6 +1089,13 @@  void test_sprintf_chk_s_nonconst (int w, int p, const char *s)
      the end (we don't print "past the end" when we're not
      sure which we can't be with an unknown string.  */
   T (1, "%1s",  s);             /* { dg-warning "writing a terminating nul past the end" } */
+
+  /* Multiple directives.  */
+  T (1, "%s%-s",    s, s);   /* { dg-warning ".%-s. directive writing likely 1 or more bytes" } */
+  T (1, "%s%-s%s", s, s, s); /* { dg-warning ".%-s. directive writing likely 1 or more bytes" } */
+  T (1, "%1s%-s%s", s, s, s); /* { dg-warning ".%-s. directive writing likely 1 or more bytes into a region of size 0" } */
+  T (1, "%s%1s%s", s, s, s); /* { dg-warning ".%1s. directive writing 1 or more bytes into a region of size between 0 and 1" } */
+  T (1, "%s%-s%1s", s, s, s); /* { dg-warning ".%-s. directive writing likely 1 or more bytes into a region of size between 0 and 1" } */
 }
 
 /* Exercise the hh length modifier with all integer specifiers and
@@ -1179,15 +1204,15 @@  void test_sprintf_chk_hh_nonconst (int w, int p, int a)
   /* The argument is known but precision isn't.  When the argument
      is zero only the first call can be diagnosed since a zero
      precision would result in no bytes on output.  */
-  T (0, "%.*hhi", p,   0);      /* { dg-warning "nul past the end" } */
-  T (1, "%.*hhi", p,   0);
+  T (0, "%.*hhi", p,   0);      /* { dg-warning " writing up to \[0-9\]+ bytes" } */
+  T (1, "%.*hhi", p,   0);      /* { dg-warning "may write a terminating nul" }*/
   T (2, "%.*hhi", p,   0);
   T (2, "%.*hhi", p,  12);      /* { dg-warning "nul past the end" } */
   T (2, "%.*hhi", p, 123);      /* { dg-warning "into a region" } */
 
   /* The argument is known but neither width nor precision is.  */
-  T (0, "%*.*hhi", w, p,   0);  /* { dg-warning "nul past the end" } */
-  T (1, "%*.*hhi", w, p,   0);
+  T (0, "%*.*hhi", w, p,   0);  /* { dg-warning "writing up to \[0-9\]+ bytes" } */
+  T (1, "%*.*hhi", w, p,   0);  /* { dg-warning "may write a terminating nul" } */
   T (2, "%*.*hhi", w, p,   0);
   T (2, "%*.*hhi", w, p,  12);  /* { dg-warning "nul past the end" } */
   T (2, "%*.*hhi", w, p, 123);  /* { dg-warning "into a region" } */
@@ -1310,7 +1335,8 @@  void test_sprintf_chk_int_nonconst (int w, int p, int a)
   T (3, "%2u",          a);
   T (3, "%2x",          a);
 
-  T (1, "%.*d",      p, a);
+  T (1, "%.*d",      p, a);     /* { dg-warning "nul past the end" } */
+  T (2, "%.*d",      p, a);
 
   T (4, "%i %i",        a, a);
   /* The following will definitely be "writing a terminating nul past the end"
@@ -1359,7 +1385,7 @@  void test_sprintf_chk_e_nonconst (int w, int p, double d)
   T ( 8, "%.1e",        d);
 
   T ( 0, "%*e",      0, d);           /* { dg-warning "writing between 12 and 14 bytes into a region of size 0" } */
-  T ( 0, "%*e",      w, d);           /* { dg-warning "writing 12 or more bytes into a region of size 0" } */
+  T ( 0, "%*e",      w, d);           /* { dg-warning "writing 12 or more bytes into a region of size 0|writing between 12 and \[0-9\]+ bytes into a region of size 0" } */
 }
 
 void test_sprintf_chk_f_nonconst (double d)
@@ -1412,23 +1438,22 @@  void test_vsprintf_chk_c (va_list va)
   T (3, "%c%c");
 
   /* Wide characters.  */
-  T (0, "%lc");             /* { dg-warning "nul past the end" } */
-  T (1, "%lc");
-  T (2, "%lc");
+  T (0, "%lc");             /* { dg-warning "up to 6 bytes " } */
+  T (1, "%lc");             /* { dg-warning "up to 6 bytes " } */
+  T (2, "%lc");             /* { dg-warning "may write a terminating nul" } */
 
   /* The following could result in as few as a single byte and in as many
      as MB_CUR_MAX, but since the MB_CUR_MAX value is a runtime property
      the write cannot be reliably diagnosed.  */
-  T (2, "%lc");
-  T (2, "%1lc");
+  T (2, "%1lc");            /* { dg-warning "may write a terminating nul" } */
   /* Writing some unknown number of bytes into a field two characters wide.  */
   T (2, "%2lc");            /* { dg-warning "nul past the end" } */
-  T (2, "%lc%lc");
+  T (2, "%lc%lc");          /* { dg-warning "writing up to 6 bytes into a region of size between 0 and 2" } */
 
-  T (3, "%lc%c");
+  T (3, "%lc%c");           /* { dg-warning "may write a terminating nul" } */
   /* Here in the best case each argument will format as single character,
      causing the terminating NUL to be written past the end.  */
-  T (3, "%lc%c%c");
+  T (3, "%lc%c%c");         /* { dg-warning "writing 1 byte into a region of size between 0 and 2" } */
 }
 
 void test_vsprintf_chk_int (va_list va)
@@ -1512,11 +1537,11 @@  void test_snprintf_c_const (char *d)
   T (2, "%2lc", (wint_t)L'1');          /* { dg-warning "output truncated before the last format character" } */
 
   T (3, "%lc%c",   (wint_t)'1', '2');
-  /* Here in the best case each argument will format as single character,
-     causing the output to be truncated just before the terminating NUL
-     (i.e., cutting off the '3').  */
-  T (3, "%lc%c%c", (wint_t)'1', '2', '3');   /* { dg-warning "output truncated" } */
-  T (3, "%lc%lc%c", (wint_t)'1', (wint_t)'2', '3'); /* { dg-warning "output truncated" } */
+  /* Here %lc may result in anywhere between 0 and MB_CUR_MAX characters
+     so the minimum number of bytes on output is 2 (plus the terminating
+     nul), but the likely number is 3 (plus the nul).  */
+  T (3, "%lc%c%c", (wint_t)'1', '2', '3');   /* { dg-warning "output may be truncated" } */
+  T (3, "%lc%lc%c", (wint_t)'1', (wint_t)'2', '3'); /* { dg-warning "output may be truncated" } */
 }
 
 #undef T
@@ -1530,7 +1555,7 @@  void test_snprintf_chk_c_const (void)
   /* Verify that specifying a size of the destination buffer that's
      bigger than its actual size (normally determined and passed to
      the function by __builtin_object_size) is diagnosed.  */
-  __builtin___snprintf_chk (buffer, 3, 0, 2, " ");   /* { dg-warning "specified bound 3 exceeds the size 2 of the destination" } */
+  FUNC (__snprintf_chk)(buffer, 3, 0, 2, " ");   /* { dg-warning "specified bound 3 exceeds the size 2 of the destination" } */
 
   T (-1, "%c",    0);           /* { dg-warning "specified bound \[0-9\]+ exceeds maximum object size \[0-9\]+" } */
 
@@ -1562,11 +1587,11 @@  void test_snprintf_chk_c_const (void)
   T (2, "%2lc", (wint_t)'1');          /* { dg-warning "output truncated before the last format character" } */
 
   T (3, "%lc%c",   (wint_t)'1', '2');
-  /* Here in the best case each argument will format as single character,
-     causing the output to be truncated just before the terminating NUL
-     (i.e., cutting off the '3').  */
-  T (3, "%lc%c%c", (wint_t)'1', '2', '3');   /* { dg-warning "output truncated" } */
-  T (3, "%lc%lc%c", (wint_t)'1', (wint_t)'2', '3'); /* { dg-warning "output truncated" } */
+  /* Here %lc may result in anywhere between 0 and MB_CUR_MAX characters
+     so the minimum number of bytes on output is 2 (plus the terminating
+     nul), but the likely number is 3 (plus the nul).  */
+  T (3, "%lc%c%c", (wint_t)'1', '2', '3');   /* { dg-warning "output may be truncated" } */
+  T (3, "%lc%lc%c", (wint_t)'1', (wint_t)'2', '3'); /* { dg-warning "output may be truncated" } */
 }
 
 /* Macro to verify that calls to __builtin_vsprintf (i.e., with no size
@@ -1581,15 +1606,15 @@  void test_vsprintf_s (va_list va)
 {
   T (-1, "%s");
 
-  T (0, "%s");              /* { dg-warning "writing a terminating nul past the end" } */
-  T (1, "%s");
+  T (0, "%s");              /* { dg-warning "writing likely 1 or more bytes" } */
+  T (1, "%s");              /* { dg-warning "may write a terminating nul" } */
   T (1, "%1s");             /* { dg-warning "writing a terminating nul past the end" } */
 
-  T (2, "%s%s");
-  T (2, "%s%s_");
-  T (2, "%s_%s");
-  T (2, "_%s%s");
-  T (2, "_%s_%s");          /* { dg-warning "writing a terminating nul past the end" } */
+  T (2, "%s%s");            /* { dg-warning "may write a terminating nul" } */
+  T (2, "%s%s_");           /* { dg-warning "writing 1 byte into a region of size between 0 and 2" } */
+  T (2, "%s_%s");           /* { dg-warning "writing likely 1 or more bytes into a region of size between 0 and 1" } */
+  T (2, "_%s%s");           /* { dg-warning "writing likely 1 or more bytes into a region of size between 0 and 1" } */
+  T (2, "_%s_%s");          /* { dg-warning "writing 1 byte into a region of size between 0 and" } */
 }
 
 /* Exercise all integer specifiers with no modifier and a non-constant
@@ -1648,14 +1673,14 @@  void test_vsnprintf_s (va_list va)
   T (-1, "%s");             /* { dg-warning "specified bound \[0-9\]+ exceeds maximum object size \[0-9\]+" } */
 
   T (0, "%s");
-  T (1, "%s");
+  T (1, "%s");              /* { dg-warning "output may be truncated" } */
   T (1, "%1s");             /* { dg-warning "output truncated before the last format character" } */
 
-  T (2, "%s%s");
-  T (2, "%s%s_");
-  T (2, "%s_%s");
-  T (2, "_%s%s");
-  T (2, "_%s_%s");          /* { dg-warning "output truncated before the last format character" } */
+  T (2, "%s%s");            /* { dg-warning "output may be truncated" } */
+  T (2, "%s%s_");           /* { dg-warning "output may be truncated writing 1 byte into a region of size between 0 and 2" } */
+  T (2, "%s_%s");           /* { dg-warning "directive output may be truncated writing likely 1 or more bytes into a region of size between 0 and 1" } */
+  T (2, "_%s%s");           /* { dg-warning "output may be truncated writing likely 1 or more bytes into a region of size between 0 and 1" } */
+  T (2, "_%s_%s");          /* { dg-warning "output may be truncated writing 1 byte into a region of size between 0 and 1" } */
 }
 
 #undef T
@@ -1669,17 +1694,17 @@  void test_vsnprintf_chk_s (va_list va)
   /* Verify that specifying a size of the destination buffer that's
      bigger than its actual size (normally determined and passed to
      the function by __builtin_object_size) is diagnosed.  */
-  __builtin___vsnprintf_chk (buffer, 123, 0, 122, "%-s", va);   /* { dg-warning "specified bound 123 exceeds the size 122 of the destination" } */
+  FUNC (__vsnprintf_chk)(buffer, 123, 0, 122, "%-s", va);   /* { dg-warning "specified bound 123 exceeds the size 122 of the destination" } */
 
-  __builtin___vsnprintf_chk (buffer, __SIZE_MAX__, 0, 2, "%-s", va);   /* { dg-warning "specified bound \[0-9\]+ exceeds maximum object size \[0-9\]+" } */
+  FUNC (__vsnprintf_chk)(buffer, __SIZE_MAX__, 0, 2, "%-s", va);   /* { dg-warning "specified bound \[0-9\]+ exceeds maximum object size \[0-9\]+" } */
 
   T (0, "%s");
-  T (1, "%s");
+  T (1, "%s");              /* { dg-warning "output may be truncated before the last format character" } */
   T (1, "%1s");             /* { dg-warning "output truncated before the last format character" } */
 
-  T (2, "%s%s");
-  T (2, "%s%s_");
-  T (2, "%s_%s");
-  T (2, "_%s%s");
-  T (2, "_%s_%s");          /* { dg-warning "output truncated before the last format character" } */
+  T (2, "%s%s");            /* { dg-warning "output may be truncated before the last format character" } */
+  T (2, "%s%s_");           /* { dg-warning "output may be truncated writing 1 byte into a region of size between 0 and 2" } */
+  T (2, "%s_%s");           /* { dg-warning "output may be truncated writing likely 1 or more bytes into a region of size between 0 and 1" } */
+  T (2, "_%s%s");           /* { dg-warning "output may be truncated writing likely 1 or more bytes into a region of size between 0 and 1" } */
+  T (2, "_%s_%s");          /* { dg-warning "output may be truncated writing 1 byte into a region of size between 0 and 1" } */
 }
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-2.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-2.c
index 7acb83d..75ee792 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-2.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-2.c
@@ -1,5 +1,5 @@ 
 /* { dg-do compile } */
-/* { dg-options "-std=c99 -Wformat -Wformat-length=2 -ftrack-macro-expansion=0" } */
+/* { dg-options "-Wformat -Wformat-length=2 -ftrack-macro-expansion=0" } */
 
 /* When debugging, define LINE to the line number of the test case to exercise
    and avoid exercising any of the others.  The buffer and objsize macros
@@ -47,12 +47,16 @@  void test_s_const (void)
   T (1, "%*ls",  0, L"\0");
   T (1, "%*ls",  1, L"");       /* { dg-warning "nul past the end" } */
 
-  T (1, "%ls",      L"1");      /* { dg-warning "directive writing between 1 and 6 bytes into a region of size 1" } */
+  /* A wide character converts into between zero and MB_LEN_MAX bytes
+     (although individual ASCII characters are assumed to convert into
+     1 bt %lc so this could be made smarter.  */
+  T (1, "%ls",      L"1");      /* { dg-warning "directive writing up to 6 bytes into a region of size 1" } */
   T (1, "%.0ls",    L"1");
   T (2, "%.0ls",    L"1");
   T (2, "%.1ls",    L"1");
   T (2, "%.2ls",    L"1");      /* { dg-warning "nul past the end" } */
-  T (2, "%.3ls",    L"1");      /* { dg-warning "directive writing between 1 and 3 bytes into a region of size 2" } */
+  T (2, "%.3ls",    L"1");      /* { dg-warning "directive writing up to 3 bytes into a region of size 2" } */
+  T (2, "%.7ls",    L"1");      /* { dg-warning "directive writing up to 6 bytes into a region of size 2" } */
   T (2, "%.2ls",    L"12");     /* { dg-warning "nul past the end" } */
 
   /* The "%.2ls" directive below will write at a minimum 1 byte (because
@@ -71,8 +75,8 @@  void test_s_const (void)
   T (3, "%.3ls",    L"12");     /* { dg-warning "nul past the end" } */
   T (4, "%.3ls",    L"123");
   T (4, "%.4ls",    L"123");    /* { dg-warning "nul past the end" } */
-  T (4, "%.5ls",    L"123");    /* { dg-warning "directive writing between 3 and 5 bytes into a region of size 4" } */
-  T (4, "%.6ls",    L"123");    /* { dg-warning "directive writing between 3 and 6 bytes into a region of size 4" } */
+  T (4, "%.5ls",    L"123");    /* { dg-warning "directive writing up to 5 bytes into a region of size 4" } */
+  T (4, "%.6ls",    L"123");    /* { dg-warning "directive writing up to 6 bytes into a region of size 4" } */
 }
 
 
@@ -87,21 +91,32 @@  struct Arrays {
 
 /* Exercise buffer overflow detection with non-const string arguments.  */
 
-void test_s_nonconst (const char *s, const wchar_t *ws, struct Arrays *a)
+void test_s_nonconst (int w, int p, const char *s, const wchar_t *ws,
+		      struct Arrays *a)
 {
   T (0, "%s",   s);             /* { dg-warning "into a region" "sprintf transformed into strcpy" { xfail *-*-* } } */
   T (1, "%s",   s);             /* { dg-warning "nul past the end" "sprintf transformed into strcpy" { xfail *-*-* } } */
-  T (1, "%1s",  s);             /* { dg-warning "nul past the end" } */
+  T (1, "%1s",  s);             /* { dg-warning "writing a terminating nul" } */
   T (1, "%.0s", s);
   T (1, "%.1s", s);             /* { dg-warning "may write a terminating nul" } */
+  T (1, "%*s", 0, s);           /* { dg-warning "may write a terminating nul" } */
+  T (1, "%*s", 1, s);           /* { dg-warning "writing a terminating nul" } */
+  T (1, "%*s", 2, s);           /* { dg-warning "directive writing 2 or more bytes" } */
+  T (1, "%*s", 3, s);           /* { dg-warning "directive writing 3 or more bytes" } */
+
+  T (1, "%.*s", 1, s);          /* { dg-warning "may write a terminating nul" } */
+  T (1, "%.*s", 2, s);          /* { dg-warning "writing up to 2 bytes" } */
+  T (1, "%.*s", 3, s);          /* { dg-warning "writing up to 3 bytes" } */
 
   T (1, "%.0ls",  ws);
   T (1, "%.1ls",  ws);          /* { dg-warning "may write a terminating nul" } */
-  T (1, "%ls",    ws);          /* { dg-warning "writing a terminating nul" } */
+  T (1, "%ls",    ws);          /* { dg-warning "may write a terminating nul" } */
 
   /* Verify that the size of the array is used in lieu of its length.
-     The minus sign disables GCC's sprintf to strcpy transformation.  */
-  T (1, "%-s", a->a1);          /* { dg-warning "nul past the end" } */
+     The minus sign disables GCC's sprintf to strcpy transformation.
+     In the case below, the length of s->a1 can be at most zero, so
+     the call should not be diagnosed.  */
+  T (1, "%-s", a->a1);
 
   /* In the following test, since the length of the strings isn't known,
      their type (the array) is used to bound the maximum length to 1,
@@ -116,8 +131,8 @@  void test_s_nonconst (const char *s, const wchar_t *ws, struct Arrays *a)
 
   /* The length of a zero length array and flexible array member is
      unknown and at leve 2 assumed to be at least 1.  */
-  T (1, "%-s", a->a0);          /* { dg-warning "nul past the end" } */
-  T (1, "%-s", a->ax);          /* { dg-warning "nul past the end" } */
+  T (1, "%-s", a->a0);          /* { dg-warning "may write a terminating nul" } */
+  T (1, "%-s", a->ax);          /* { dg-warning "may write a terminating nul" } */
 
   T (2, "%-s", a->a0);
   T (2, "%-s", a->ax);
@@ -145,20 +160,20 @@  void test_hh_nonconst (int w, int p, int x, unsigned y)
 
   /* Zero precision means that zero argument formats as no bytes unless
      length or flags make it otherwise.  */
-  T (1, "%.*hhi",    0, x);     /* { dg-warning "between 0 and 4 bytes" } */
-  T (2, "%.*hhi",    0, x);     /* { dg-warning "between 0 and 4 bytes" } */
-  T (3, "%.*hhi",    0, x);     /* { dg-warning "between 0 and 4 bytes" } */
+  T (1, "%.*hhi",    0, x);     /* { dg-warning "writing up to 4 bytes" } */
+  T (2, "%.*hhi",    0, x);     /* { dg-warning "writing up to 4 bytes" } */
+  T (3, "%.*hhi",    0, x);     /* { dg-warning "writing up to 4 bytes" } */
   T (4, "%.*hhi",    0, x);     /* { dg-warning "may write a terminating nul past the end of the destination" } */
 
-  T (1, "%.*hhi",    0, y);     /* { dg-warning "between 0 and 4 bytes" } */
-  T (2, "%.*hhi",    0, y);     /* { dg-warning "between 0 and 4 bytes" } */
-  T (3, "%.*hhi",    0, y);     /* { dg-warning "between 0 and 4 bytes" } */
+  T (1, "%.*hhi",    0, y);     /* { dg-warning "writing up to 4 bytes" } */
+  T (2, "%.*hhi",    0, y);     /* { dg-warning "writing up to 4 bytes" } */
+  T (3, "%.*hhi",    0, y);     /* { dg-warning "writing up to 4 bytes" } */
   T (4, "%.*hhi",    0, y);     /* { dg-warning "may write a terminating nul past the end of the destination" } */
 
-  T (1, "%#.*hhi",    0, y);    /* { dg-warning "between 0 and 4 bytes" } */
+  T (1, "%#.*hhi",    0, y);    /* { dg-warning "writing up to 4 bytes" } */
   /* { dg-warning ".#. flag used" "-Wformat" { target *-*-* } .-1 } */
   T (1, "%+.*hhi",    0, y);    /* { dg-warning "between 1 and 4 bytes" } */
-  T (1, "%-.*hhi",    0, y);    /* { dg-warning "between 0 and 4 bytes" } */
+  T (1, "%-.*hhi",    0, y);    /* { dg-warning "writing up to 4 bytes" } */
   T (1, "% .*hhi",    0, y);    /* { dg-warning "between 1 and 4 bytes" } */
 
   T (1, "%#.*hhi",    1, y);    /* { dg-warning "between 1 and 4 bytes" } */
@@ -167,18 +182,18 @@  void test_hh_nonconst (int w, int p, int x, unsigned y)
   T (1, "%-.*hhi",    1, y);    /* { dg-warning "between 1 and 4 bytes" } */
   T (1, "% .*hhi",    1, y);    /* { dg-warning "between 2 and 4 bytes" } */
 
-  T (1, "%#.*hhi",    p, y);    /* { dg-warning "writing 0 or more bytes" } */
+  T (1, "%#.*hhi",    p, y);    /* { dg-warning "writing up to \[0-9\]+ bytes" } */
   /* { dg-warning ".#. flag used" "-Wformat" { target *-*-* } .-1 } */
-  T (1, "%+.*hhi",    p, y);    /* { dg-warning "writing 1 or more bytes" } */
-  T (1, "%-.*hhi",    p, y);    /* { dg-warning "writing 0 or more bytes" } */
-  T (1, "% .*hhi",    p, y);    /* { dg-warning "writing 1 or more bytes" } */
+  T (1, "%+.*hhi",    p, y);    /* { dg-warning "writing 1 or more bytes|writing between 1 and \[0-9\]+ bytes" } */
+  T (1, "%-.*hhi",    p, y);    /* { dg-warning "writing up to \[0-9\]+ bytes" } */
+  T (1, "% .*hhi",    p, y);    /* { dg-warning "writing between 1 and \[0-9\]+ bytes|writing 1 or more bytes" } */
 
-  T (1, "%#.*hhu",    0, y);    /* { dg-warning "between 0 and 3 bytes" } */
+  T (1, "%#.*hhu",    0, y);    /* { dg-warning "writing up to 3 bytes" } */
   /* { dg-warning ".#. flag used" "-Wformat" { target *-*-* } .-1 } */
-  T (1, "%+.*hhu",    0, y);    /* { dg-warning "between 0 and 3 bytes" } */
+  T (1, "%+.*hhu",    0, y);    /* { dg-warning "writing up to 3 bytes" } */
   /* { dg-warning ".\\+. flag used" "-Wformat" { target *-*-* } .-1 } */
-  T (1, "%-.*hhu",    0, y);    /* { dg-warning "between 0 and 3 bytes" } */
-  T (1, "% .*hhu",    0, y);    /* { dg-warning "between 0 and 3 bytes" } */
+  T (1, "%-.*hhu",    0, y);    /* { dg-warning "writing up to 3 bytes" } */
+  T (1, "% .*hhu",    0, y);    /* { dg-warning "writing up to 3 bytes" } */
   /* { dg-warning ". . flag used" "-Wformat" { target *-*-* } .-1 } */
 }
 
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c
index 00a3c7d..afa9aad 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c
@@ -47,7 +47,7 @@  void test_sprintf_chk_string (const char *s, const char *t)
   T (1, "%s", x ? "1" : "");       /* { dg-warning "nul past the end" } */
   T (1, "%s", x ? s : "1");        /* { dg-warning "nul past the end" } */
   T (1, "%s", x ? "1" : s);        /* { dg-warning "nul past the end" } */
-  T (1, "%s", x ? s : t);
+  T (1, "%s", x ? s : t);          /* { dg-warning "may write a terminating nul" } */
 
   T (2, "%s", x ? "" : "1");
   T (2, "%s", x ? "" : s);
@@ -198,10 +198,10 @@  void test_sprintf_chk_range_schar (void)
   T ( 3, "%i",  R (  0,  99));
   T ( 3, "%i",  R (  0, 100));  /* { dg-warning "may write a terminating nul past the end of the destination" } */
 
-  /* The following call may write as few as 3 bytes and as many as 5.
+  /* The following call may write as few as 2 bytes and as many as 4.
      It's a judgment call how best to diagnose it to make the potential
      problem clear.  */
-  T ( 3, "%i%i", R (1, 10), R (9, 10));   /* { dg-warning "may write a terminating nul past the end|.%i. directive writing between 1 and 2 bytes into a region of size 1" } */
+  T ( 3, "%i%i", R (1, 10), R (9, 10));   /* { dg-warning "directive writing between 1 and 2 bytes into a region of size between 1 and 2" } */
 
   T ( 4, "%i%i", R (10, 11), R (12, 13));   /* { dg-warning "nul past the end" } */
 
@@ -211,7 +211,11 @@  void test_sprintf_chk_range_schar (void)
   T ( 6, "%i_%i_%i", R (0, 9), R (0, 9), R (0, 10));  /* { dg-warning "may write a terminating nul past the end" } */
   T ( 6, "%i_%i_%i", R (0, 9), R (0, 10), R (0, 9));  /* { dg-warning "may write a terminating nul past the end" } */
   T ( 6, "%i_%i_%i", R (0, 10), R (0, 9), R (0, 9));  /* { dg-warning "may write a terminating nul past the end" } */
-  T ( 6, "%i_%i_%i", R (0, 9), R (0, 10), R (0, 10)); /* { dg-warning "may write a terminating nul past the end|.%i. directive writing between 1 and 2 bytes into a region of size 1" } */
+  T ( 6, "%hhi_%hi_%i", R (0, 9), R (0, 10), R (0, 10)); /* { dg-warning ".i. directive writing between 1 and 2 bytes into a region of size between 1 and 2" } */
+  T ( 6, "%3i|%2i/%1i", R (0, 99), R (0, 99), R (0, 99)); /* { dg-warning "./. directive writing 1 byte into a region of size 0" } */
+  T ( 6, "%.3i|%.2i/%i", R (0, 99), R (0, 99), R (0, 99)); /* { dg-warning "./. directive writing 1 byte into a region of size 0" } */
+  T ( 6, "%.3i|%.2i/%i", R (0, 119), R (0, 99), R (0, 99)); /* { dg-warning "./. directive writing 1 byte into a region of size 0" } */
+  T ( 6, "%.3i|%.2i/%i", R (0, 1), R (0, 2), R (0, 3)); /* { dg-warning "./. directive writing 1 byte into a region of size 0" } */
 }
 
 void test_sprintf_chk_range_uchar (void)
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c
index fd0a90a..66fc0a7 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c
@@ -19,7 +19,7 @@  void test (void)
      { dg-end-multiline-output "" }
      { dg-begin-multiline-output "-Wformat-length output" }
    sprintf (dst + 2, "1", 0);
-                      ~^
+                       ^
      { dg-end-multiline-output "" }
      { dg-begin-multiline-output "note" }
    sprintf (dst + 2, "1", 0);
@@ -70,7 +70,7 @@  void test (void)
      { dg-message ".sprintf. output 2 bytes into a destination of size 1" "note" { target *-*-* } .-2 }
      { dg-begin-multiline-output "-Wformat-length output" }
    sprintf (dst + 2, "%-s", "1");
-                      ~~~^
+                         ^
      { dg-end-multiline-output "" }
      { dg-begin-multiline-output "note" }
    sprintf (dst + 2, "%-s", "1");
@@ -160,7 +160,7 @@  void test_sprintf_note (void)
   /* { dg-warning "41: writing a terminating nul past the end of the destination" "" { target *-*-* } .-1 }
      { dg-begin-multiline-output "" }
    __builtin_sprintf (buffer (6), "%c%s%i", '1', "2", 3456);
-                                   ~~~~~~^
+                                         ^
      { dg-end-multiline-output "" }
 
      { dg-message ".__builtin_sprintf. output 7 bytes into a destination of size 6" "" { target *-*-* } .-7 }
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-7.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-7.c
index 0069348..0649038 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-7.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-7.c
@@ -47,7 +47,10 @@  void test_floating_a_cst (void)
   T (0, "%*a",  INT_MIN, 0.);     /* { dg-warning "writing 2147483648 bytes" } */
   T (0, "%*a",  INT_MAX, 0.);     /* { dg-warning "writing 2147483647 bytes" } */
 
-  T (0, "%.*a", INT_MIN, 0.);     /* { dg-warning "writing 6 bytes" } */
+  /* %a is poorly specified and as a result some implementations trim
+     redundant trailing zeros (e.g., Glibc) and others don't (e.g.,
+     Solaris).  */
+  T (0, "%.*a", INT_MIN, 0.);     /* { dg-warning "writing between 6 and 20 bytes" } */
 
   T (0, "%.*a", INT_MAX, 0.);     /* { dg-warning "writing 2147483654 bytes" } */
 
@@ -75,7 +78,7 @@  void test_floating_e_cst (void)
   T (0, "%*e",  INT_MIN, 0.);     /* { dg-warning "writing 2147483648 bytes" } */
   T (0, "%*e",  INT_MAX, 0.);     /* { dg-warning "writing 2147483647 bytes" } */
 
-  T (0, "%.*e", INT_MIN, 0.);     /* { dg-warning "writing 5 bytes" } */
+  T (0, "%.*e", INT_MIN, 0.);     /* { dg-warning "writing 12 bytes" } */
 
   T (0, "%.*e", INT_MAX, 0.);     /* { dg-warning "writing 2147483653 bytes" } */
 
@@ -103,7 +106,7 @@  void test_floating_f_cst (void)
   T (0, "%*f",  INT_MIN, 0.);     /* { dg-warning "writing 2147483648 bytes" } */
   T (0, "%*f",  INT_MAX, 0.);     /* { dg-warning "writing 2147483647 bytes" } */
 
-  T (0, "%.*f", INT_MIN, 0.);     /* { dg-warning "writing 1 byte" } */
+  T (0, "%.*f", INT_MIN, 0.);     /* { dg-warning "writing 8 bytes" } */
 
   T (0, "%.*f", INT_MAX, 0.);     /* { dg-warning "writing 2147483649 bytes" } */
 
@@ -170,14 +173,14 @@  void test_string_cst (void)
 
 void test_string_var (const char *s)
 {
-  T (0, "%*s",  INT_MIN, s);     /* { dg-warning "writing 2147483648 bytes" } */
-  T (0, "%*s",  INT_MAX, s);     /* { dg-warning "writing 2147483647 bytes" } */
+  T (0, "%*s",  INT_MIN, s);     /* { dg-warning "writing 2147483648 or more bytes" } */
+  T (0, "%*s",  INT_MAX, s);     /* { dg-warning "writing 2147483647 or more bytes" } */
 
-  T (0, "%.*s", INT_MIN, s);     /* { dg-warning "writing a terminating nul" } */
+  T (0, "%.*s", INT_MIN, s);     /* { dg-warning "writing likely 1 or more bytes" } */
 
-  T (0, "%.*s", INT_MAX, s);     /* { dg-warning "writing between 0 and 2147483647 bytes" } */
+  T (0, "%.*s", INT_MAX, s);     /* { dg-warning "writing up to 2147483647 bytes" } */
 
-  T (0, "%*.*s", INT_MIN, INT_MIN, s);   /* { dg-warning "writing 2147483648 bytes" } */
+  T (0, "%*.*s", INT_MIN, INT_MIN, s);   /* { dg-warning "writing 2147483648 or more bytes" } */
 
   T (0, "%*.*s", INT_MAX, INT_MAX, s);   /* { dg-warning "writing 2147483647 bytes" } */
 }
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-9.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-9.c
new file mode 100644
index 0000000..0cf92c3
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-9.c
@@ -0,0 +1,158 @@ 
+/* { dg-do compile } */
+/* { dg-options "-O2 -Wformat -Wformat-length=2 -ftrack-macro-expansion=0" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+#define INT_MAX   __INT_MAX__
+#define INT_MIN   (-INT_MAX - 1)
+
+#ifndef LINE
+#  define LINE 0
+#endif
+
+int dummy_sprintf (char*, const char*, ...);
+void sink (void*);
+
+char buffer[4096];
+char *ptr;
+
+/* Helper to expand function to either __builtin_f or dummy_f to
+   make debugging GCC easy.  */
+#define FUNC(f)							\
+  ((!LINE || LINE == __LINE__) ? __builtin_ ## f : dummy_ ## f)
+
+/* Evaluate to an array of SIZE characters when non-negative, or to
+   a pointer to an unknown object otherwise.  */
+#define buffer(size)					\
+  ((0 <= size) ? buffer + sizeof buffer - (size) : ptr)
+
+#define T(bufsize, fmt, ...)						\
+  do {									\
+    char *buf = buffer (bufsize);					\
+    FUNC (sprintf)(buf, fmt, __VA_ARGS__);				\
+    sink (buf);								\
+  } while (0)
+
+
+/* Identity function to verify that the checker figures out the value
+   of the operand even when it's not constant (i.e., makes use of
+   inlining and constant propagation information).  */
+
+int i (int x) { return x; }
+const char* s (const char *str) { return str; }
+
+/* Function to "generate" a unique unknown number (as far as GCC can
+   tell) each time it's called.  It prevents the optimizer from being
+   able to narrow down the ranges of possible values in test functions
+   with repeated references to the same variable.  */
+extern int value (void);
+
+/* Return a value in the range [MIN, MAX].  */
+int range (int min, int max)
+{
+  int val = value ();
+  return val < min || max < val ? min : val;
+}
+
+#define R(min, max) range (min, max)
+
+/* Verify that the checker can detect buffer overflow when the "%s"
+   argument is in a known range of lengths and one or both of which
+   exceed the size of the destination.  */
+
+void test_sprintf_chk_string (const char *s)
+{
+  T (1, "%*s", R (0, 1), "");     /* { dg-warning "may write a terminating nul" } */
+  T (1, "%*s", R (-2, -1), "");   /* { dg-warning "writing up to 2 bytes" } */
+  T (1, "%*s", R (-3,  2), "");   /* { dg-warning "writing up to 3 bytes" } */
+  T (1, "%*s", R (-4,  5), "");   /* { dg-warning "writing up to 5 bytes" } */
+
+  T (1, "%*s", R ( -5, 6), "1");  /* { dg-warning "writing between 1 and 6 bytes" } */
+  T (1, "%*s", R ( -6, 7), "12"); /* { dg-warning "writing between 2 and 7 bytes" } */
+
+  T (1, "%.*s", R (0, 1), "");
+  T (1, "%.*s", R (0, 1), s);     /* { dg-warning "may write a terminating nul" } */
+  T (1, "%.*s", R (-2, -1), "");
+  T (1, "%.*s", R (-2, -1), s);   /* { dg-warning "may write a terminating nul" } */
+  T (1, "%.*s", R (-3,  2), "");
+  T (1, "%.*s", R (-4,  5), "");
+
+  T (1, "%.*s", R ( -5, 6), "1");  /* { dg-warning "may write a terminating nul" } */
+  T (1, "%.*s", R ( -6, 7), "12"); /* { dg-warning "writing up to 2 bytes " } */
+  T (1, "%.*s", R (  1, 7), "12"); /* { dg-warning "writing between 1 and 2 bytes " } */
+  T (1, "%.*s", R (  2, 7), "12"); /* { dg-warning "writing 2 bytes " } */
+
+  T (1, "%*.*s", R (0, 1), R (0, 1), "");     /* { dg-warning "may write a terminating nul" } */
+  T (1, "%*.*s", R (0, 2), R (0, 1), "");     /* { dg-warning "directive writing up to 2 bytes into a region of size 1" } */
+  T (1, "%*.*s", R (0, 3), R (0, 1), "");     /* { dg-warning "writing up to 3 bytes" } */
+
+  T (1, "%*.*s", R (0, 1), R (0, 1), "1");    /* { dg-warning "may write a terminating nul" } */
+  T (1, "%*.*s", R (0, 2), R (0, 1), "1");    /* { dg-warning "writing up to 2 bytes" } */
+  T (1, "%*.*s", R (0, 3), R (0, 1), "1");    /* { dg-warning "writing up to 3 bytes" } */
+
+  T (1, "%*.*s", R (0, 1), R (0, 1), "12");   /* { dg-warning "may write a terminating nul" } */
+  T (1, "%*.*s", R (0, 2), R (0, 1), "12");   /* { dg-warning "writing up to 2 bytes" } */
+  T (1, "%*.*s", R (0, 3), R (0, 1), "12");   /* { dg-warning "writing up to 3 bytes" } */
+
+  T (1, "%*.*s", R (0, 1), R (0, 1), "123");  /* { dg-warning "may write a terminating nul" } */
+  T (1, "%*.*s", R (0, 2), R (0, 1), "123");  /* { dg-warning "writing up to 2 bytes" } */
+  T (1, "%*.*s", R (0, 3), R (0, 1), "123");  /* { dg-warning "writing up to 3 bytes" } */
+  T (1, "%*.*s", R (0, 3), R (0, 1), s);      /* { dg-warning "writing up to 3 bytes" } */
+
+  T (1, "%*.*s", R (0, 1), R (0, 2), "123");  /* { dg-warning "writing up to 2 bytes" } */
+  T (1, "%*.*s", R (0, 2), R (0, 2), "123");  /* { dg-warning "writing up to 2 bytes" } */
+  T (1, "%*.*s", R (0, 3), R (0, 2), "123");  /* { dg-warning "writing up to 3 bytes" } */
+  T (1, "%*.*s", R (0, 3), R (0, 2), s);      /* { dg-warning "writing up to 3 bytes" } */
+
+  T (1, "%*.*s", R (0, 1), R (0, 3), "123");  /* { dg-warning "writing up to 3 bytes" } */
+  T (1, "%*.*s", R (0, 2), R (0, 3), "123");  /* { dg-warning "writing up to 3 bytes" } */
+  T (1, "%*.*s", R (0, 3), R (0, 3), "123");  /* { dg-warning "writing up to 3 bytes" } */
+  T (1, "%*.*s", R (0, 3), R (0, 3), s);      /* { dg-warning "writing up to 3 bytes" } */
+
+  T (1, "%*.*s", R (1, 1), R (0, 3), "123");  /* { dg-warning "writing between 1 and 3 bytes" } */
+  T (1, "%*.*s", R (1, 2), R (0, 3), "123");  /* { dg-warning "writing between 1 and 3 bytes" } */
+  T (1, "%*.*s", R (1, 3), R (0, 3), "123");  /* { dg-warning "writing between 1 and 3 bytes" } */
+  T (1, "%*.*s", R (1, 3), R (0, 3), s);      /* { dg-warning "writing between 1 and 3 bytes" } */
+
+  T (1, "%*.*s", R (1, 1), R (1, 3), "123");  /* { dg-warning "writing between 1 and 3 bytes" } */
+  T (1, "%*.*s", R (1, 2), R (1, 3), "123");  /* { dg-warning "writing between 1 and 3 bytes" } */
+  T (1, "%*.*s", R (1, 3), R (1, 3), "123");  /* { dg-warning "writing between 1 and 3 bytes" } */
+  T (1, "%*.*s", R (1, 3), R (1, 3), s);      /* { dg-warning "writing between 1 and 3 bytes" } */
+
+  T (1, "%*.*s", R (2, 3), R (1, 3), "123");  /* { dg-warning "writing between 2 and 3 bytes" } */
+  T (1, "%*.*s", R (3, 4), R (1, 3), "123");  /* { dg-warning "writing between 3 and 4 bytes" } */
+  T (1, "%*.*s", R (4, 5), R (1, 3), "123");  /* { dg-warning "writing between 4 and 5 bytes" } */
+  T (1, "%*.*s", R (2, 3), R (1, 3), s);      /* { dg-warning "writing between 2 and 3 bytes" } */
+}
+
+void test_sprintf_chk_int (int w, int p, int i)
+{
+  T (1, "%*d", w, 0);             /* { dg-warning "may write a terminating nul|directive writing between 1 and \[0-9\]+ bytes" } */
+  T (1, "%*d", w, i);             /* { dg-warning "may write a terminating nul|directive writing between 1 and \[0-9\]+ bytes" } */
+
+  T (1, "%*d", R (-1, 1), 0);     /* { dg-warning "writing a terminating nul" } */
+  T (1, "%*d", R ( 0, 1), 0);     /* { dg-warning "writing a terminating nul" } */
+  T (1, "%+*d", R ( 0, 1), 0);    /* { dg-warning "directive writing 2 bytes" } */
+  T (1, "%+*u", R ( 0, 1), 0);    /* { dg-warning "writing a terminating nul" } */
+  T (2, "%*d", R (-3, -2), 0);     /* { dg-warning "directive writing between 1 and 3 bytes" } */
+  T (2, "%*d", R (-3, -1), 0);     /* { dg-warning "directive writing between 1 and 3 bytes" } */
+  T (2, "%*d", R (-3,  0), 0);     /* { dg-warning "directive writing between 1 and 3 bytes" } */
+  T (2, "%*d", R (-2, -1), 0);     /* { dg-warning "may write a terminating nul" } */
+  T (2, "%*d", R (-2,  2), 0);     /* { dg-warning "may write a terminating nul" } */
+  T (2, "%*d", R (-1,  2), 0);     /* { dg-warning "may write a terminating nul" } */
+  T (2, "%*d", R ( 0,  2), 0);     /* { dg-warning "may write a terminating nul" } */
+  T (2, "%*d", R ( 1,  2), 0);     /* { dg-warning "may write a terminating nul" } */
+
+  T (1, "%.*d", p, 0);             /* { dg-warning "may write a terminating nul|directive writing up to \[0-9\]+ bytes" } */
+  T (1, "%.*d", p, i);             /* { dg-warning "may write a terminating nul||directive writing up to \[0-9\]+ bytes" } */
+  T (1, "%.*d", R (INT_MIN, -1), 0);     /* { dg-warning "writing a terminating nul" } */
+  T (1, "%.*d", R (INT_MIN,  0), 0);     /* { dg-warning "may write a terminating nul" } */
+  T (1, "%.*d", R (-2, -1), 0);     /* { dg-warning "writing a terminating nul" } */
+  T (1, "%.*d", R (-1,  1), 0);     /* { dg-warning "may write a terminating nul" } */
+  T (1, "%.*d", R ( 0,  1), 0);     /* { dg-warning "may write a terminating nul" } */
+  T (1, "%.*d", R ( 0,  2), 0);     /* { dg-warning "directive writing up to 2 bytes" } */
+  T (1, "%.*d", R ( 0,  INT_MAX - 1), 0);     /* { dg-warning "directive writing up to \[0-9\]+ bytes" } */
+  T (1, "%.*d", R ( 1,  INT_MAX - 1), 0);     /* { dg-warning "directive writing between 1 and \[0-9\]+ bytes" } */
+}
+
+/* { dg-prune-output "flag used with .%.. gnu_printf format" } */
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf.c
index 35a5bd0..b559090 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf.c
@@ -411,14 +411,19 @@  test_a_double (double d)
   EQL ( 6,  7, "%.0a", 0.0);        /* 0x0p+0 */
   EQL ( 6,  7, "%.0a", 1.0);        /* 0x8p-3 */
   EQL ( 6,  7, "%.0a", 2.0);        /* 0x8p-2 */
-  EQL ( 8,  9, "%.1a", 3.0);        /* 0xc.0p-2 */
-  EQL ( 9, 10, "%.2a", 4.0);        /* 0x8.00p-1 */
-  EQL (10, 11, "%.3a", 5.0);        /* 0xa.000p-1 */
+
+  /* The decimal point may be up to MB_LEN_MAX long.  */
+  RNG ( 8, 13, 14, "%.1a", 3.0);    /* 0xc.0p-2 */
+  RNG ( 9, 14, 15, "%.2a", 4.0);    /* 0x8.00p-1 */
+  RNG (10, 15, 16, "%.3a", 5.0);    /* 0xa.000p-1 */
 
 	                            /* d is in [ 0, -DBL_MAX ] */
   RNG ( 6, 10, 11, "%.0a", d);      /* 0x0p+0 ... -0x2p+1023 */
-  RNG ( 6, 12, 13, "%.1a", d);      /* 0x0p+0 ... -0x2.0p+1023 */
-  RNG ( 6, 13, 14, "%.2a", d);      /* 0x0p+0 ... -0x2.00p+1023 */
+  /* %a is poorly specified and allows for implementations divergence:
+     some (such as Glibc) trim redundant trailing zeros after decimal
+     point and others (e.g., Solaris) don't.  */
+  RNG ( 6, 30, 31, "%.1a", d);      /* 0x0p+0 ... -0x2.0...0p+1023 */
+  RNG ( 6, 30, 31, "%.2a", d);      /* 0x0p+0 ... -0x2.00...0p+1023 */
 }
 
 static void __attribute__ ((noinline, noclone))
@@ -427,111 +432,113 @@  test_a_long_double (void)
   EQL ( 6,  7, "%.0La", 0.0L);      /* 0x0p+0 */
   EQL ( 6,  7, "%.0La", 1.0L);      /* 0x8p-3 */
   EQL ( 6,  7, "%.0La", 2.0L);      /* 0x8p-2 */
-  EQL ( 8,  9, "%.1La", 3.0L);      /* 0xc.0p-2 */
-  EQL ( 9, 10, "%.2La", 4.0L);      /* 0xa.00p-1 */
+
+  RNG ( 8, 13, 14, "%.1La", 3.0L);  /* 0xc.0p-2 */
+  RNG ( 9, 14, 15, "%.2La", 4.0L);  /* 0xa.00p-1 */
 }
 
 static void __attribute__ ((noinline, noclone))
 test_e_double (void)
 {
-  EQL (12, 13, "%e",  1.0e0);
-  EQL (13, 14, "%e", -1.0e0);
-  EQL (12, 13, "%e",  1.0e+1);
-  EQL (13, 14, "%e", -1.0e+1);
-  EQL (12, 13, "%e",  1.0e+12);
-  EQL (13, 14, "%e", -1.0e+12);
-  EQL (13, 14, "%e",  1.0e+123);
-  EQL (14, 15, "%e", -1.0e+123);
-
-  EQL (12, 13, "%e",  9.999e+99);
-  EQL (12, 13, "%e",  9.9999e+99);
-  EQL (12, 13, "%e",  9.99999e+99);
+  RNG (12, 17, 18, "%e",  1.0e0);
+  RNG (13, 18, 19, "%e", -1.0e0);
+  RNG (12, 17, 18, "%e",  1.0e+1);
+  RNG (13, 18, 19, "%e", -1.0e+1);
+  RNG (12, 17, 18, "%e",  1.0e+12);
+  RNG (13, 18, 19, "%e", -1.0e+12);
+  RNG (13, 18, 19, "%e",  1.0e+123);
+  RNG (14, 19, 20, "%e", -1.0e+123);
+
+  RNG (12, 17, 18, "%e",  9.999e+99);
+  RNG (12, 17, 18, "%e",  9.9999e+99);
+  RNG (12, 17, 18, "%e",  9.99999e+99);
 
   /* The actual output of the following directive depends on the rounding
      mode.  */
-  /* EQL (12, "%e",  9.9999994e+99); */
+  /* RNG (12, "%e",  9.9999994e+99); */
 
-  EQL (12, 13, "%e",  1.0e-1);
-  EQL (12, 13, "%e",  1.0e-12);
-  EQL (13, 14, "%e",  1.0e-123);
+  RNG (12, 17, 18, "%e",  1.0e-1);
+  RNG (12, 17, 18, "%e",  1.0e-12);
+  RNG (13, 18, 19, "%e",  1.0e-123);
 }
 
 static void __attribute__ ((noinline, noclone))
 test_e_long_double (void)
 {
-  EQL (12, 13, "%Le",  1.0e0L);
-  EQL (13, 14, "%Le", -1.0e0L);
-  EQL (12, 13, "%Le",  1.0e+1L);
-  EQL (13, 14, "%Le", -1.0e+1L);
-  EQL (12, 13, "%Le",  1.0e+12L);
-  EQL (13, 14, "%Le", -1.0e+12L);
-  EQL (13, 14, "%Le",  1.0e+123L);
-  EQL (14, 15, "%Le", -1.0e+123L);
-
-  EQL (12, 13, "%Le",  9.999e+99L);
-  EQL (12, 13, "%Le",  9.9999e+99L);
-  EQL (12, 13, "%Le",  9.99999e+99L);
+  RNG (12, 17, 18, "%Le",  1.0e0L);
+  RNG (13, 18, 19, "%Le", -1.0e0L);
+  RNG (12, 17, 18, "%Le",  1.0e+1L);
+  RNG (13, 18, 19, "%Le", -1.0e+1L);
+  RNG (12, 18, 19, "%Le",  1.0e+12L);
+  RNG (13, 19, 20, "%Le", -1.0e+12L);
+  RNG (13, 19, 20, "%Le",  1.0e+123L);
+  RNG (14, 20, 21, "%Le", -1.0e+123L);
+
+  RNG (12, 18, 19, "%Le",  9.999e+99L);
+  RNG (12, 18, 19, "%Le",  9.9999e+99L);
+  RNG (12, 18, 19, "%Le",  9.99999e+99L);
 
 #if __DBL_DIG__ < __LDBL_DIG__
-  EQL (12, 13, "%Le",  9.999999e+99L);
+  RNG (12, 17, 18, "%Le",  9.999999e+99L);
 #else
-  RNG (12, 13, 14, "%Le",  9.999999e+99L);
+  RNG (12, 18, 19, "%Le",  9.999999e+99L);
 #endif
 
   /* The actual output of the following directive depends on the rounding
      mode.  */
-  /* EQL (12, "%Le",  9.9999994e+99L); */
+  /* RNG (12, "%Le",  9.9999994e+99L); */
 
-  EQL (12, 13, "%Le",  1.0e-1L);
-  EQL (12, 13, "%Le",  1.0e-12L);
-  EQL (13, 14, "%Le",  1.0e-123L);
+  RNG (12, 17, 18, "%Le",  1.0e-1L);
+  RNG (12, 17, 18, "%Le",  1.0e-12L);
+  RNG (13, 18, 19, "%Le",  1.0e-123L);
 
   EQL ( 6,  7, "%.0Le",   1.0e-111L);
-  EQL ( 8,  9, "%.1Le",   1.0e-111L);
-  EQL (19, 20, "%.12Le",  1.0e-112L);
-  EQL (20, 21, "%.13Le",  1.0e-113L);
+
+  RNG ( 8, 13, 14, "%.1Le",   1.0e-111L);
+  RNG (19, 25, 25, "%.12Le",  1.0e-112L);
+  RNG (20, 26, 27, "%.13Le",  1.0e-113L);
 }
 
 static void __attribute__ ((noinline, noclone))
 test_f_double (void)
 {
-  EQL (  8,   9, "%f", 0.0e0);
-  EQL (  8,   9, "%f", 0.1e0);
-  EQL (  8,   9, "%f", 0.12e0);
-  EQL (  8,   9, "%f", 0.123e0);
-  EQL (  8,   9, "%f", 0.1234e0);
-  EQL (  8,   9, "%f", 0.12345e0);
-  EQL (  8,   9, "%f", 0.123456e0);
-  EQL (  8,   9, "%f", 1.234567e0);
-
-  EQL (  9,  10, "%f", 1.0e+1);
-  EQL ( 20,  21, "%f", 1.0e+12);
-  EQL (130, 131, "%f", 1.0e+123);
-
-  EQL (  8,   9, "%f", 1.0e-1);
-  EQL (  8,   9, "%f", 1.0e-12);
-  EQL (  8,   9, "%f", 1.0e-123);
+  RNG (  8,  13,  14, "%f", 0.0e0);
+  RNG (  8,  13,  14, "%f", 0.1e0);
+  RNG (  8,  13,  14, "%f", 0.12e0);
+  RNG (  8,  13,  14, "%f", 0.123e0);
+  RNG (  8,  13,  14, "%f", 0.1234e0);
+  RNG (  8,  13,  14, "%f", 0.12345e0);
+  RNG (  8,  13,  14, "%f", 0.123456e0);
+  RNG (  8,  13,  14, "%f", 1.234567e0);
+
+  RNG (  9,  14,  15, "%f", 1.0e+1);
+  RNG ( 20,  26,  27, "%f", 1.0e+12);
+  RNG (130, 136, 137, "%f", 1.0e+123);
+
+  RNG (  8,  13,  14, "%f", 1.0e-1);
+  RNG (  8,  13,  14, "%f", 1.0e-12);
+  RNG (  8,  13,  14, "%f", 1.0e-123);
 }
 
 static void __attribute__ ((noinline, noclone))
 test_f_long_double (void)
 {
-  EQL (  8,   9, "%Lf", 0.0e0L);
-  EQL (  8,   9, "%Lf", 0.1e0L);
-  EQL (  8,   9, "%Lf", 0.12e0L);
-  EQL (  8,   9, "%Lf", 0.123e0L);
-  EQL (  8,   9, "%Lf", 0.1234e0L);
-  EQL (  8,   9, "%Lf", 0.12345e0L);
-  EQL (  8,   9, "%Lf", 0.123456e0L);
-  EQL (  8,   9, "%Lf", 1.234567e0L);
-
-  EQL (  9,  10, "%Lf", 1.0e+1L);
-  EQL ( 20,  21, "%Lf", 1.0e+12L);
-  EQL (130, 131, "%Lf", 1.0e+123L);
-
-  EQL (  8,   9, "%Lf", 1.0e-1L);
-  EQL (  8,   9, "%Lf", 1.0e-12L);
-  EQL (  8,   9, "%Lf", 1.0e-123L);
+  RNG (  8,  15,  16, "%Lf", 0.0e0L);
+  RNG (  8,  14,  15, "%Lf", 0.1e0L);
+  RNG (  8,  14,  15, "%Lf", 0.12e0L);
+  RNG (  8,  14,  15, "%Lf", 0.123e0L);
+  RNG (  8,  14,  15, "%Lf", 0.1234e0L);
+  RNG (  8,  14,  15, "%Lf", 0.12345e0L);
+  RNG (  8,  14,  15, "%Lf", 0.123456e0L);
+  RNG (  8,  14,  15, "%Lf", 1.234567e0L);
+
+  RNG (  9,  15,  16, "%Lf", 1.0e+1L);
+  RNG ( 20,  26,  27, "%Lf", 1.0e+12L);
+  RNG (130, 136, 137, "%Lf", 1.0e+123L);
+
+  RNG (  8,  14,  15, "%Lf", 1.0e-1L);
+  RNG (  8,  14,  15, "%Lf", 1.0e-12L);
+  RNG (  8,  14,  15, "%Lf", 1.0e-123L);
 }
 
 static void __attribute__ ((noinline, noclone))
diff --git a/gcc/vec.h b/gcc/vec.h
index 3877f2d..02063d0 100644
--- a/gcc/vec.h
+++ b/gcc/vec.h
@@ -1619,7 +1619,6 @@  vec<T, va_heap, vl_ptr>::safe_grow_cleared (unsigned len MEM_STAT_DECL)
     }
 }
 
-
 /* Same as vec::safe_grow but without reallocation of the internal vector.
    If the vector cannot be extended, a runtime assertion will be triggered.  */