diff mbox

- improve sprintf buffer overflow detection (middle-end/49905)

Message ID 578D512F.9050909@gmail.com
State New
Headers show

Commit Message

Martin Sebor July 18, 2016, 9:59 p.m. UTC
>> Thanks for the clarification.  IIUC, there are at least three
>> possibilities for how to proceed: leave it as is (no checking
>> with LTO), link LTO with C_COMMON_OBJS, or move the c-format.c
>> code into the middle end.  Do you have a preference for one of
>> these?  Or is there another solution that I missed?
>
> Another solution is to implement this somewhen before LTO
> bytecode is streamed out thus at the end of early optimizations.

The warning relies on object size checking to determine the size
of the destination buffers, and on constant propagation and range
information from the VRP pass to avoid false positives for bounded
integer values.  Given that, running it (only) early would fairly
severely limit its usefulness.

> I'm not sure linking with C_COMMON_OBJS does even work (you can try).
> Likewise c-format.c may be too entangled with the FEs (maybe just
> linking with c-format.o is enough?).

Unfortunately, linking with C_COMMON_OBJS isn't enough and linking
with C_OBJS doesn't work because of multiple definitions for symbols
like gt_ggc_mx_language_function.  I don't know this part of of GCC
but it seems that each front end gets a set of these generated
functions, some with the same names.  I spent a couple of hours
trying to get it to work but eventually gave up.

The only option left to make it work with LTO is to extract the
checker from c-format.c and move it where it gets linked with lto1.

To that end, the attached patch adds the checker under its own new
pass.  The pass runs very early without optimization, and relatively
late with it to benefit from the passes above.  With optimization
the pass also can (but doesn't yet) fold the return value from these
functions into a constant.  In the Linux kernel, it folds 88 snprintf
calls (I didn't find any instances where the whole call could be
folded into a string constant).

I tested the patch with LTO but due to bug 71907 no warnings are
issued.  Once the bug is resolved I'll re-test it and see about
adding test cases to verify it.

In the updated patch I tried to address the majority of everyone's
comments, including those I got from the bug submitter.  I also
made a number of enhancements based on the results I saw with the
Linux kernel and other code (e.g., support for %p, additional
heuristics to improve %s coverage, and support for POSIX numbered
arguments).

Once a patch along these lines is approved and committed, I'd like
to enhance it by adding one or more of the following:

  *  -fdump-sprintf-length option to have the pass dump details
     about opportunities to fold expressions as well as instances
     where the checker was unable to check a call because of lack
     of object size or argument value or range information.

  *  Support for the return value folding (I have implemented and
     lightly tested thid but I would prefer to treat it it as
     a separate enhancement independent of this one).

  *  If/when David's patch for on-demand locations within string
     literals is accepted and committed
       https://gcc.gnu.org/ml/gcc-patches/2016-07/msg00441.html
     replace the location handling code I copied from c-format.c
     with the new API.

  *  Enhance the heuristics usesd to find likely %s argument
     lengths to improve the checker's coverage.

  *  Add support for %n and perhaps other functions (e.g., scanf).

Thanks
Martin

Comments

Jeff Law July 21, 2016, 8:33 p.m. UTC | #1
On 07/18/2016 03:59 PM, Martin Sebor wrote:
>
> Unfortunately, linking with C_COMMON_OBJS isn't enough and linking
> with C_OBJS doesn't work because of multiple definitions for symbols
> like gt_ggc_mx_language_function.  I don't know this part of of GCC
> but it seems that each front end gets a set of these generated
> functions, some with the same names.  I spent a couple of hours
> trying to get it to work but eventually gave up.
Correct.  gt_ggc_mx_* are generated functions to support the garbage 
collection system.  In this specific case, I think it's the GC bits for 
struct language_function which is specific to each front-end.  So each 
front-end is going to have that function hence the multiple definitions.


> The only option left to make it work with LTO is to extract the
> checker from c-format.c and move it where it gets linked with lto1.
I think you're right.

>
> To that end, the attached patch adds the checker under its own new
> pass.  The pass runs very early without optimization, and relatively
> late with it to benefit from the passes above.  With optimization
> the pass also can (but doesn't yet) fold the return value from these
> functions into a constant.  In the Linux kernel, it folds 88 snprintf
> calls (I didn't find any instances where the whole call could be
> folded into a string constant).
So I haven't dug into the patch yet, but what (if any) interaction is 
there between the early and late passes?  Do they both emit warnings, or 
is it more that the early passe builds a candidate set which is refined 
by the later pass, or something totally different?


>
> I tested the patch with LTO but due to bug 71907 no warnings are
> issued.  Once the bug is resolved I'll re-test it and see about
> adding test cases to verify it.
Note Richi checked in a patch a couple days ago to stream the abstract 
origins, which is supposed to fix 71907.


>
> In the updated patch I tried to address the majority of everyone's
> comments, including those I got from the bug submitter.  I also
> made a number of enhancements based on the results I saw with the
> Linux kernel and other code (e.g., support for %p, additional
> heuristics to improve %s coverage, and support for POSIX numbered
> arguments).
>
> Once a patch along these lines is approved and committed, I'd like
> to enhance it by adding one or more of the following:
>
>  *  -fdump-sprintf-length option to have the pass dump details
>     about opportunities to fold expressions as well as instances
>     where the checker was unable to check a call because of lack
>     of object size or argument value or range information.
That'll definitely be useful as we've discussed in the past.

>
>  *  Support for the return value folding (I have implemented and
>     lightly tested thid but I would prefer to treat it it as
>     a separate enhancement independent of this one).
Seems reasonable to me.

>
>  *  If/when David's patch for on-demand locations within string
>     literals is accepted and committed
>       https://gcc.gnu.org/ml/gcc-patches/2016-07/msg00441.html
>     replace the location handling code I copied from c-format.c
>     with the new API.
Also sounds good.

>
>  *  Enhance the heuristics usesd to find likely %s argument
>     lengths to improve the checker's coverage.
>
>  *  Add support for %n and perhaps other functions (e.g., scanf).
Also good stuff.

Onward to looking at the patch :-)

jeff
Jeff Law July 21, 2016, 9:48 p.m. UTC | #2
I saw a few places in GCC itself where you increased buffer sizes.  Were 
any of those level 1 failures?

In c.opt:

> +C ObjC C++ ObjC++ Warning Alias (Wformat-length=, 1, 0)
Can't this take on values 0, 1, 2? And if so, is the "1" above correct?

In invoke.texi we have:

+-Wno-format-contains-nul -Wno-format-extra-args -Wformat-length=2 @gol
Note Wformat-length=2.

Then in the actual documentation it says the default level is 1.
> +in output truncation.  GCC counts the number of bytes that each format
> +string and directive within it writes into the provided buffer and, when
> +it detects that more bytes that fit in the destination buffer may be output,
> +it emits a warning.

s/that fit/than fit

In that same section is the text which says the default level is 1

I'm curious about the heuristics for level 1.  I guess if you get a 
warning at level 1, then you've got a situation where we know we'll 
write out of bounds.  level 2 is a may write out of bounds.  Which makes 
me wonder if rather than levels we should use symbolic names?

I guess my worry here is that if we don't message this right folks will 
make their code level 1 clean and think they're done.  When in reality 
all they've done is mitigate the most egregious problems.

Elsewhere in invoke.texi:

> +buffer in an inforational note following the warning.
informational I assume

> +the minimum buffer size.  For exampe, if @var{a} and @var{b} above can
example

In genmatch.c:
> +      char id[13];   /* Big enough for a 32-bit UINT_MAX.  */
In general we don't add comments to the end of a line like that.  If a 
comment is needed, put it before the declaration.  I'm not sure if a 
comment is needed here or not -- your call.

Similarly for various other places were you increased buffer lengths.


In passes.c:

> +
> +      // inform (0, "executing pass: %s", pass->name);
> +
>        if (execute_one_pass (pass) && pass->sub)
> -        execute_pass_list_1 (pass->sub);
> +       {
> +         // inform (0, "executing subpass: %s", pass->sub->name);
> +         execute_pass_list_1 (pass->sub);
> +       }
> +


The comments left over from debugging?

Looks like ~1/3 of the patch are tests.  Good stuff.  I'm going to 
assume those are correct.

I'll get into gimple-ssa-sprintf.c either later tonight or tomorrow. 
But so far nothing major, just nits.

Jeff
Martin Sebor July 21, 2016, 9:57 p.m. UTC | #3
>> To that end, the attached patch adds the checker under its own new
>> pass.  The pass runs very early without optimization, and relatively
>> late with it to benefit from the passes above.  With optimization
>> the pass also can (but doesn't yet) fold the return value from these
>> functions into a constant.  In the Linux kernel, it folds 88 snprintf
>> calls (I didn't find any instances where the whole call could be
>> folded into a string constant).
> So I haven't dug into the patch yet, but what (if any) interaction is
> there between the early and late passes?  Do they both emit warnings, or
> is it more that the early passe builds a candidate set which is refined
> by the later pass, or something totally different?

Thanks for the feedback.

The pass only runs once.  Without optimization, it's invoked early,
does its thing, and is never invoked again.  With optimization,
it's also invoked early but returns without doing anything, only
to to invoked again much later on to do real work.  I wasn't sure
where among all the passes might be the best spot to insert it so
I put in the same place as Aldy's -Walloca patch because it relies
on the same prior passes.

>> I tested the patch with LTO but due to bug 71907 no warnings are
>> issued.  Once the bug is resolved I'll re-test it and see about
>> adding test cases to verify it.
> Note Richi checked in a patch a couple days ago to stream the abstract
> origins, which is supposed to fix 71907.

Thanks for the heads up.  I'll give it a whirl.

> Onward to looking at the patch :-)

Re-reading it again myself I see a few typos, commented out chunks
of code left over from debugging, and missing comments.  I'll clean
this up in the next update along with other requested changes.

Martin
Jeff Law July 22, 2016, 7:18 p.m. UTC | #4
On 07/21/2016 03:57 PM, Martin Sebor wrote:
>
> The pass only runs once.  Without optimization, it's invoked early,
> does its thing, and is never invoked again.  With optimization,
> it's also invoked early but returns without doing anything, only
> to to invoked again much later on to do real work.  I wasn't sure
> where among all the passes might be the best spot to insert it so
> I put in the same place as Aldy's -Walloca patch because it relies
> on the same prior passes.
Seems reasonable.  I'm not sure were best to put the late call either; 
it's a balancing act many passes have opportunities to simplify stuff in 
ways that may make a false positive go away, but many passes can also 
muck up the range information that you're likely depending on.

I'd say stick with its current position in the pipeline, then we should 
reevaluate after some of the range reimplementation lands.

jeff
Jakub Jelinek July 22, 2016, 7:57 p.m. UTC | #5
On Mon, Jul 18, 2016 at 03:59:11PM -0600, Martin Sebor wrote:
> +  /* Try to use __builtin_object_size although it rarely returns
> +     a useful result even for straighforward cases.  */
> +  tree ost = warn_format_length < 2
> +    ? integer_zero_node : build_int_cst (size_type_node, 2);
> +  tree args[] = { dest, ost };
> +  tree func = builtin_decl_explicit (BUILT_IN_OBJECT_SIZE);
> +  if (tree size = fold_builtin_n (UNKNOWN_LOCATION, func, args, 2, false))

What is the point of going through fold etc.?  You can just
(for ADDR_EXPR and SSA_NAME) call compute_builtin_object_size without,
and don't have to convert the result back to tree and then back to uhwi.

> +    return tree_to_uhwi (STRIP_NOPS  (size));

Formatting.
> +
> +  /* If __builtin_object_size fails to deliver, try to compute
> +     it for the very basic (but common) cases.  */
> +  if (TREE_CODE (dest) == SSA_NAME
> +      && POINTER_TYPE_P (TREE_TYPE (dest)))
> +    {
> +      gimple *def = SSA_NAME_DEF_STMT (dest);
> +      if (gimple_code (def) == GIMPLE_ASSIGN)

is_gimple_assign (def) ?

> +	{
> +	  tree_code code = gimple_assign_rhs_code (def);
> +	  if (code == POINTER_PLUS_EXPR)
> +	    {
> +	      tree off = gimple_assign_rhs2 (def);
> +	      dest = gimple_assign_rhs1 (def);
> +
> +	      if (cst_and_fits_in_hwi (off))
> +		{
> +		  unsigned HOST_WIDE_INT size = get_destination_size (dest);
> +		  if (size != HOST_WIDE_INT_M1U)
> +		    return size - tree_to_shwi (off);
> +		}

I think you need to be very careful on negative offsets here (or don't allow
them).

> +	    }

Shouldn't this have some upper bound for the recursion?
E.g. PARAM_VALUE (PARAM_MAX_SSA_NAME_QUERY_DEPTH)?
> +	}
> +    }
> +
> +  return -1;
> +}

	Jakub
Jeff Law July 22, 2016, 10:12 p.m. UTC | #6
Working through the new pass...  Overall it looks pretty good.  There's 
a certain level of trust I'll extend WRT getting the low level details 
right -- a thorough testsuite obviously helps there.



> +const pass_data pass_data_sprintf_length = {
> +  GIMPLE_PASS,        // pass type
> +  "sprintf_length",   // pass name
> +  OPTGROUP_NONE,      // optinfo_flags
> +  TV_NONE,            // tv_id
> +  PROP_cfg,           // properties_required
> +  0,                 // properties_provided
> +  0,                 // properties_destroyed
> +  0,                 // properties_start
> +  0,                 // properties_finish
So the worry here is that you need PROP_ssa in the late pass.  I'm not 
sure if we have the infrastructure to allow you to distinguish the two. 
I guess we could literally make it two passes with two distinct 
pass_data structures.


In handle_gimple_call, you don't handle anti ranges -- I haven't looked 
closely at canonicalization rules in VRP, so I don't know if you're 
likely to see a range like ![MININT, -1] which would be a synonym for 
[0..MAXINT].  It might be worth doing some instrumentation to see if 
you're getting useful anti-ranges with any kind of consistency.  ISTM 
the most interesting ones are going to allow you to drop or insist on a 
"-" sign.



> +      /* As a special case, bounded function allow the explicitly
> +        specified destination size argument to be zero as a request
> +        to determine the number of bytes on output without actually
> +        writing any output.  Disable length checking for those.  */
This doesn't parse well.


> +  /* Try to use __builtin_object_size although it rarely returns
> +     a useful result even for straighforward cases.  */
Hopefully you've stashed away some tests for this so that we can work on 
them independently of the sprintf checking itself?  ISTM that the 
recursive handling of POINTER_PLUS_EXPR really ought to move into the 
generic builtin_object_size handling itself as an independent change.



> +  /* Bail early if format length checking is disabled, either
> +     via a command line option, or as a result of the size of
> +     the destination object not being available, or due to
> +     format directives or arguments encountered during processing
> +     that prevent length tracking with any reliability.  */
> +
> +  if (HOST_WIDE_INT_MAX <= info.objsize)
> +      return;
I think the return is indented too deep.

> +      if (*pf == 0)
> +       {
> +         /* Incomplete directive.  */
> +         return;
> +       }
For this and the other early returns from compute_format_length, do we 
know if -Wformat will catch these errors?  Might that be a low priority 
follow-up project if it doesn't?


> +static void
> +add_bytes (const pass_sprintf_length::call_info &info,
> +          const char                           *beg,
> +          const char                           *end,
> +          format_result                        *res)
In general, don't try to line up parameters, args or variable 
declarations like you've done here.  Similarly in other places.

> +  /* if (warn_format_length */
> +  /*     && (overflowed || navail < nbytes */
> +  /*     || (1 < warn_format_length && )) */
Presumably old implementation and/or debugging code...  Please remove it.

Please check indention of the curleys & code block in the loop over the 
phi arguments in get_string_length.


In format_floating, we end up using actual host floating point 
arithmetic.  That's generally been frowned upon.  We're not doing 
anything in terms of code generation here, so ultimately that might be 
what allows us to still be safe from a cross compilation standpoint.

It's also not clear if that routine handles the other target floating 
point formats.  For things like VAX FP, I'm comfortable issuing a 
warning that this stuff isn't supported on that target.   We have other 
targets where a double might be the same size as a float.   Though I 
guess in that case the worst we get is false positives, so that may not 
be a huge issue either.  I'm not sure how to check for this stuff 
without bringing in aspects of the target though, which we'd like to avoid.

In format_integer you have target specific ifdefs (solaris and aix). 
First we want to avoid conditionally compiled code.  Second, from a 
modularity something like TARGET_AIX and TARGET_SOLARIS really aren't 
appropriate in here. I suspect what you're going to want to do is add 
something to the targetm structure that you can query and/or call here 
is the best we can do.

Again, overall it looks good.  My biggest concern is format_integer and 
format_float and the bleeding of target dependencies into this code.  To 
some degree it may be unavoidable and we can mitigate the damage with 
stuff in targetm -- I'd like to hear your thoughts.

Jeff
Joseph Myers July 29, 2016, 10:50 p.m. UTC | #7
On Mon, 18 Jul 2016, Martin Sebor wrote:

> +  /* Number of exponent digits or -1 when unknown.  */
> +  int expdigs = -1;
> +  /* 1 when ARG < 0, 0 when ARG >= 0, -1 when unknown.  */
> +  int negative = -1;
> +  /* Log10 of EXPDIGS.  */
> +  int logexpdigs = 2;
> +
> +  const double log10_2 = .30102999566398119521;
> +
> +  if (arg && TREE_CODE (arg) == REAL_CST)
> +    {
> +      expdigs = real_exponent (TREE_REAL_CST_PTR (arg)) * log10_2;
> +      negative = real_isneg (TREE_REAL_CST_PTR (arg));
> +      logexpdigs = ilog (expdigs, 10);

This looks wrong for the case of a constant with a negative exponent.

Also, what if the constant has a decimal floating-point type?

> +    }
> +  else if (REAL_MODE_FORMAT (TYPE_MODE (type))->b == 2)
> +    {
> +      /* Compute T_MAX_EXP for base 2.  */
> +      expdigs = REAL_MODE_FORMAT (TYPE_MODE (type))->emax * log10_2;

Shouldn't you also compute logexpdigs here?  The comment on logexpdigs 
implies it's always meant to have a given relation to expdigs.  Or maybe 
those variables need to be split into min/max cases, since you're 
computing both min/max values below.

> +    case 'E':
> +    case 'e':
> +      /* The minimum output is "[-+]1.234567e+00" for an IEEE double
> +	 regardless of the value of the actual argument. */
> +      res.min = ((0 < negative || spec.get_flag ('+') || spec.get_flag (' '))
> +		 + 1 /* unit */ + (prec < 0 ? 7 : prec ? prec + 1 : 0)
> +		 + 2 /* e+ */ + (logexpdigs < 2 ? 2 : logexpdigs));

As I understand your logic, for the case of a constant expdigs and so 
logexpdigs may sometimes be 1 bigger than the correct value for that 
constant, say for 9.999e+99, so this may not actually be the minimum.

> +      /* The maximum output is "-1.234567e+123" for a double and one
> +	 more byte for a large exponent for a long louble.  */
> +      res.max = negative < 0 ? res.min + 2 + (spec.get_flag ('L')) : res.min;

No, don't embed such assumptions about particular formats.  Compute 
correct max/min values based on the exponent range of the format in 
question.

> +      /* The maximum depends on the magnitude of the value but it's
> +	 at most 316 bytes for double and 4940 for long double, plus
> +	 precision if non-negative, or 6.  */

I don't think comments should embed such assumptions, either.

> +    case 'G':
> +    case 'g':
> +      /* Treat this the same as '%F' for now even though that's
> +	 inaccurate.  */
> +      res.min = 2 + (prec < 0 ? 6 : prec);
> +      res.max = ((spec.get_flag ('L') ? 4934 : 310)
> +		 + (prec < 0 ? 6 : prec ? prec + 1 : 0));

Again, avoid embedding properties of given formats.

(I realise tree-call-cdce.c embeds some such assumptions, but that's bad 
practice that should not be repeated.)
Martin Sebor Aug. 4, 2016, 9:13 p.m. UTC | #8
On 07/21/2016 03:48 PM, Jeff Law wrote:
>

Sorry about the delay in responding...

> I saw a few places in GCC itself where you increased buffer sizes.  Were
> any of those level 1 failures?

Yes.  With optimization, even level 1 uses range information when
it's available.  The idea is that when the range of values of the
variable has been constrained (possibly to avoid a sprintf buffer
overflow) but wasn't constrained enough, it's a possible sign of
a bug.

In some cases such as when the resulting range is the result of
a cast from signed to unsigned (as in cp/mangle.c) it triggers
a diagnostic that wouldn't be issued in its absence.  Here's
a small example:

   char d [9];

   void f (long *x)
   {
     // warning here
     __builtin_sprintf (d, "%08lx", (unsigned long)*x);
   }

   void g (long *x)
   {
     // no warning here
     __builtin_sprintf (d, "%08lx", *x);
   }

It's probably possible to tweak the pass to try to detect this
case and avoid the warning, but I'm not sure it's worth it.  What
do you think?

>
> In c.opt:
>
>> +C ObjC C++ ObjC++ Warning Alias (Wformat-length=, 1, 0)
> Can't this take on values 0, 1, 2? And if so, is the "1" above correct?
>
> In invoke.texi we have:
>
> +-Wno-format-contains-nul -Wno-format-extra-args -Wformat-length=2 @gol
> Note Wformat-length=2.
>
> Then in the actual documentation it says the default level is 1.
>> +in output truncation.  GCC counts the number of bytes that each format
>> +string and directive within it writes into the provided buffer and, when
>> +it detects that more bytes that fit in the destination buffer may be
>> output,
>> +it emits a warning.
>
> s/that fit/than fit
>
> In that same section is the text which says the default level is 1

Good catch!  The default level is meant to be 1, so the manual was
wrong. I've fixed it.

>
> I'm curious about the heuristics for level 1.  I guess if you get a
> warning at level 1, then you've got a situation where we know we'll
> write out of bounds.  level 2 is a may write out of bounds.  Which makes
> me wonder if rather than levels we should use symbolic names?
>
> I guess my worry here is that if we don't message this right folks will
> make their code level 1 clean and think they're done.  When in reality
> all they've done is mitigate the most egregious problems.

Right.  I'm not sure what the right solution is.  The biggest
difference between the levels is in the values they assume
unknown integers can take on.  Level one assumes 1, level two
assumes a value that results in the longest output (such as
INT_MIN).  This can cause false positives but in my experience
there aren't too many of those.  I would be comfortable making
level 2 the default if we had good range information.  Without
it, though, even some obviously safe cases tend to be diagnosed,
like this one:

   char d [4];

   void g (int i)
   {
     if (0 <= i && i < 1000)
       __builtin_sprintf (d, "%i", i);
   }

   warning: ‘%i’ directive writing between 1 and 11 bytes into a region 
of size 4 [-Wformat-length=]
        __builtin_sprintf (d, "%i", i);
                               ^~
   note: using the range [‘1’, ‘-2147483648’] for directive argument
   note: format output between 2 and 12 bytes into a destination of size 4

Maybe leave level 1 the default for now and bump it up to 2 when
the new and improved VRP pass is in?

> In passes.c:
>
>> +
>> +      // inform (0, "executing pass: %s", pass->name);
>> +
>>        if (execute_one_pass (pass) && pass->sub)
>> -        execute_pass_list_1 (pass->sub);
>> +       {
>> +         // inform (0, "executing subpass: %s", pass->sub->name);
>> +         execute_pass_list_1 (pass->sub);
>> +       }
>> +
>
>
> The comments left over from debugging?

Yes.  Someone else pointed it out in the first patch and I forgot
to remove it.  It's gone now.

>
> Looks like ~1/3 of the patch are tests.  Good stuff.  I'm going to
> assume those are correct.

They should be for the most part.  As we've discussed, I used actual
sprintf calls to validate them within GCC itself.  Now that I've
removed those calls from the GCC source I need to add tests to verify
that the return value computed by the pass for the sprintf calls is
what the call actually returns.  I have added a new test that does
that.  I'll post an updated patch soon.

Martin
Jeff Law Aug. 8, 2016, 7:30 p.m. UTC | #9
On 08/04/2016 03:13 PM, Martin Sebor wrote:
> On 07/21/2016 03:48 PM, Jeff Law wrote:
>>
>
> Sorry about the delay in responding...
>
>> I saw a few places in GCC itself where you increased buffer sizes.  Were
>> any of those level 1 failures?
>
> Yes.  With optimization, even level 1 uses range information when
> it's available.  The idea is that when the range of values of the
> variable has been constrained (possibly to avoid a sprintf buffer
> overflow) but wasn't constrained enough, it's a possible sign of
> a bug.
>
> In some cases such as when the resulting range is the result of
> a cast from signed to unsigned (as in cp/mangle.c) it triggers
> a diagnostic that wouldn't be issued in its absence.  Here's
> a small example:
>
>   char d [9];
>
>   void f (long *x)
>   {
>     // warning here
>     __builtin_sprintf (d, "%08lx", (unsigned long)*x);
>   }
>
>   void g (long *x)
>   {
>     // no warning here
>     __builtin_sprintf (d, "%08lx", *x);
>   }
>
> It's probably possible to tweak the pass to try to detect this
> case and avoid the warning, but I'm not sure it's worth it.  What
> do you think?
I don't think it's worth it to detect and avoid the warning right now. 
That may change as the warning sees more widespread use after going onto 
the trunk.


>
> Right.  I'm not sure what the right solution is.  The biggest
> difference between the levels is in the values they assume
> unknown integers can take on.  Level one assumes 1, level two
> assumes a value that results in the longest output (such as
> INT_MIN).  This can cause false positives but in my experience
> there aren't too many of those.  I would be comfortable making
> level 2 the default if we had good range information.  Without
> it, though, even some obviously safe cases tend to be diagnosed,
> like this one:
>
>   char d [4];
>
>   void g (int i)
>   {
>     if (0 <= i && i < 1000)
>       __builtin_sprintf (d, "%i", i);
>   }
>
>   warning: ‘%i’ directive writing between 1 and 11 bytes into a region
> of size 4 [-Wformat-length=]
>        __builtin_sprintf (d, "%i", i);
>                               ^~
>   note: using the range [‘1’, ‘-2147483648’] for directive argument
>   note: format output between 2 and 12 bytes into a destination of size 4
>
> Maybe leave level 1 the default for now and bump it up to 2 when
> the new and improved VRP pass is in?
This is probably as much of a user education problem as anything.  I 
definitely want us to be level 1 clean for gcc-7.  I think we'll want to 
evaluate the pain of getting to level 2 clean once Andrew's work is far 
enough along to evaluate how many false positives it allows us to eliminate.

jeff
Martin Sebor Aug. 11, 2016, 9:56 p.m. UTC | #10
On 07/29/2016 04:50 PM, Joseph Myers wrote:
> On Mon, 18 Jul 2016, Martin Sebor wrote:
>
>> +  /* Number of exponent digits or -1 when unknown.  */
>> +  int expdigs = -1;
>> +  /* 1 when ARG < 0, 0 when ARG >= 0, -1 when unknown.  */
>> +  int negative = -1;
>> +  /* Log10 of EXPDIGS.  */
>> +  int logexpdigs = 2;
>> +
>> +  const double log10_2 = .30102999566398119521;
>> +
>> +  if (arg && TREE_CODE (arg) == REAL_CST)
>> +    {
>> +      expdigs = real_exponent (TREE_REAL_CST_PTR (arg)) * log10_2;
>> +      negative = real_isneg (TREE_REAL_CST_PTR (arg));
>> +      logexpdigs = ilog (expdigs, 10);

Thanks for looking at it!  The floating conversion wasn't 100%
complete and the bits that were there I had the least confidence
in so I especially appreciate a second pair of eyes on it.

>
> This looks wrong for the case of a constant with a negative exponent.

You're right -- great catch!  I've fixed it.

>
> Also, what if the constant has a decimal floating-point type?

Decimal floating point isn't handled in this initial implementation.
The checker gives up after the first directive it doesn't recognize
and stops checking the rest of the format string.  Eventually, I'd
like to enhance it to handle decimal floats and other directives
and types.

>
>> +    }
>> +  else if (REAL_MODE_FORMAT (TYPE_MODE (type))->b == 2)
>> +    {
>> +      /* Compute T_MAX_EXP for base 2.  */
>> +      expdigs = REAL_MODE_FORMAT (TYPE_MODE (type))->emax * log10_2;
>
> Shouldn't you also compute logexpdigs here?  The comment on logexpdigs
> implies it's always meant to have a given relation to expdigs.  Or maybe
> those variables need to be split into min/max cases, since you're
> computing both min/max values below.

I have reworked this so logexpdigs is computed here as well.

>
>> +    case 'E':
>> +    case 'e':
>> +      /* The minimum output is "[-+]1.234567e+00" for an IEEE double
>> +	 regardless of the value of the actual argument. */
>> +      res.min = ((0 < negative || spec.get_flag ('+') || spec.get_flag (' '))
>> +		 + 1 /* unit */ + (prec < 0 ? 7 : prec ? prec + 1 : 0)
>> +		 + 2 /* e+ */ + (logexpdigs < 2 ? 2 : logexpdigs));
>
> As I understand your logic, for the case of a constant expdigs and so
> logexpdigs may sometimes be 1 bigger than the correct value for that
> constant, say for 9.999e+99, so this may not actually be the minimum.

Yes, %e is an approximation though I hadn't considered this case.
I reworked this part of the implementation to compute an accurate
result.

>> +    case 'G':
>> +    case 'g':
>> +      /* Treat this the same as '%F' for now even though that's
>> +	 inaccurate.  */
>> +      res.min = 2 + (prec < 0 ? 6 : prec);
>> +      res.max = ((spec.get_flag ('L') ? 4934 : 310)
>> +		 + (prec < 0 ? 6 : prec ? prec + 1 : 0));
>
> Again, avoid embedding properties of given formats.

I've removed the IEEE 754 assumptions from both the code and the
comments.

I will post the latest version of the patch shortly.

Martin
Martin Sebor Aug. 11, 2016, 11:36 p.m. UTC | #11
On 07/22/2016 04:12 PM, Jeff Law wrote:
> Working through the new pass...  Overall it looks pretty good.  There's
> a certain level of trust I'll extend WRT getting the low level details
> right -- a thorough testsuite obviously helps there.

In the latest patch where I add the return value optimization I also
add a new test that compares the return value computed by the pass
to the value returned by the library call.  That greatly increases
my confidence in the correctness of the pass (though the test still
could stand to be enhanced in a number of ways).

>> +const pass_data pass_data_sprintf_length = {
>> +  GIMPLE_PASS,        // pass type
>> +  "sprintf_length",   // pass name
>> +  OPTGROUP_NONE,      // optinfo_flags
>> +  TV_NONE,            // tv_id
>> +  PROP_cfg,           // properties_required
>> +  0,                 // properties_provided
>> +  0,                 // properties_destroyed
>> +  0,                 // properties_start
>> +  0,                 // properties_finish
> So the worry here is that you need PROP_ssa in the late pass.  I'm not
> sure if we have the infrastructure to allow you to distinguish the two.
> I guess we could literally make it two passes with two distinct
> pass_data structures.

I'm not sure I understand the concern.  It sounds like you are
suggesting to add another pass_data struct with PROP_ssa among
the required properties and specify it as an argument to
the gimple_opt pass constructor.  What will it do that what
I have doesn't (make sure the ssa pass runs before this pass?)
and how can I test it?

>
> In handle_gimple_call, you don't handle anti ranges -- I haven't looked
> closely at canonicalization rules in VRP, so I don't know if you're
> likely to see a range like ![MININT, -1] which would be a synonym for
> [0..MAXINT].  It might be worth doing some instrumentation to see if
> you're getting useful anti-ranges with any kind of consistency.  ISTM
> the most interesting ones are going to allow you to drop or insist on a
> "-" sign.

I don't recall seeing any inverted ranges when I was developing
the range tests.  I had high hopes for the range information but
it turned out to be so poor that I decided not to spend time
completing this part.  Once Andrew's new VRP pass is available
I'll come back to it.  If it's as generic as it sounds this pass
will not only be a consumer but also be able to contribute to
improving it.

>> +      /* As a special case, bounded function allow the explicitly
>> +        specified destination size argument to be zero as a request
>> +        to determine the number of bytes on output without actually
>> +        writing any output.  Disable length checking for those.  */
> This doesn't parse well.
>
>
>> +  /* Try to use __builtin_object_size although it rarely returns
>> +     a useful result even for straighforward cases.  */
> Hopefully you've stashed away some tests for this so that we can work on
> them independently of the sprintf checking itself?  ISTM that the
> recursive handling of POINTER_PLUS_EXPR really ought to move into the
> generic builtin_object_size handling itself as an independent change.

Bug 71831 tracks this limitation/enhancement.  I would expect to
be relatively easy to enhance the compute_builtin_object_size
function to return more useful results for some basic expressions
even without optimization, without running the whole objsize pass.
It seems that a good number of other features in GCC (not just
warnings) would benefit from it.

>> +  /* Bail early if format length checking is disabled, either
>> +     via a command line option, or as a result of the size of
>> +     the destination object not being available, or due to
>> +     format directives or arguments encountered during processing
>> +     that prevent length tracking with any reliability.  */
>> +
>> +  if (HOST_WIDE_INT_MAX <= info.objsize)
>> +      return;
> I think the return is indented too deep.
>
>> +      if (*pf == 0)
>> +       {
>> +         /* Incomplete directive.  */
>> +         return;
>> +       }
> For this and the other early returns from compute_format_length, do we
> know if -Wformat will catch these errors?  Might that be a low priority
> follow-up project if it doesn't?

Being invoked by the front end, -Wformat is limited to checking
constant strings, so it misses problems that a pass that runs
later could detect.  With a bit of effort (to avoid duplicate
warnings) it would be possible to enhance this pass to catch some
these as well.  I chose not to spend time on it in this version
since non-constant format strings are rare but it I agree that
it's something to look into after this patch is in.

> In format_floating, we end up using actual host floating point
> arithmetic.  That's generally been frowned upon.  We're not doing
> anything in terms of code generation here, so ultimately that might be
> what allows us to still be safe from a cross compilation standpoint.
>
> It's also not clear if that routine handles the other target floating
> point formats.  For things like VAX FP, I'm comfortable issuing a
> warning that this stuff isn't supported on that target.   We have other
> targets where a double might be the same size as a float.   Though I
> guess in that case the worst we get is false positives, so that may not
> be a huge issue either.  I'm not sure how to check for this stuff
> without bringing in aspects of the target though, which we'd like to avoid.
>
> In format_integer you have target specific ifdefs (solaris and aix).
> First we want to avoid conditionally compiled code.  Second, from a
> modularity something like TARGET_AIX and TARGET_SOLARIS really aren't
> appropriate in here. I suspect what you're going to want to do is add
> something to the targetm structure that you can query and/or call here
> is the best we can do.
>
> Again, overall it looks good.  My biggest concern is format_integer and
> format_float and the bleeding of target dependencies into this code.  To
> some degree it may be unavoidable and we can mitigate the damage with
> stuff in targetm -- I'd like to hear your thoughts.

I've reworked the patch to avoid making assumptions about the floating
point format and to remove the #ifdefs and use target hooks instead.
I decided to use MPFR for the floating point stuff.  I tried a couple
of other approaches but in the end I think the MPFR route will yield
the most reliable results.

I'm still running tests but once I'm done I'll post the updated patch
for review.

Thanks
Martin
diff mbox

Patch

PR middle-end/49905 - Better sanity checking on sprintf src & dest to
  produce warning for dodgy code

gcc/c-family/ChangeLog:

	PR middle-end/49905
	* c-ada-spec.c (dump_ada_function_declaration): Increase buffer size.
	* c.opt (-Wformat-length): Add new option.

gcc/cp/ChangeLog:

	PR middle-end/49905
	* mangle.c (write_real_cst): Increase buffer size.

gcc/testsuite/ChangeLog:

	PR middle-end/49905
	* gcc.dg/format/c99-sprintf-length-1.c: New test.
	* gcc.dg/format/c99-sprintf-length-2.c: New test.
	* gcc.dg/format/c99-sprintf-length-opt.c: New test.

gcc/ChangeLog:

	PR middle-end/49905
	* Makefile.in (OBJS): Add gimple-ssa-sprintf.o.
	* gimple-ssa-sprintf.c: New file.
	* passes.def: Add pass_sprintf_length,
	* tree-pass.h (make_pass_sprintf_length): Declare.
	* doc/invoke.texi (-Wformat-length): Document.
	* genmatch.c (parser::parse_expr): Increase buffer size.
	* gimplify.c (gimplify_asm_expr): Same.
	* passes.c (pass_manager::register_one_dump_file): Same.
	* print-tree.c (print_node): Same.

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 0786fa3..f740598 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1284,6 +1284,7 @@  OBJS = \
 	gimple-ssa-nonnull-compare.o \
 	gimple-ssa-split-paths.o \
 	gimple-ssa-strength-reduction.o \
+	gimple-ssa-sprintf.o \
 	gimple-streamer-in.o \
 	gimple-streamer-out.o \
 	gimple-walk.o \
diff --git a/gcc/c-family/c-ada-spec.c b/gcc/c-family/c-ada-spec.c
index e33fdff..4e8138b 100644
--- a/gcc/c-family/c-ada-spec.c
+++ b/gcc/c-family/c-ada-spec.c
@@ -1466,7 +1466,7 @@  dump_ada_function_declaration (pretty_printer *buffer, tree func,
 {
   tree arg;
   const tree node = TREE_TYPE (func);
-  char buf[16];
+  char buf[17];
   int num = 0, num_args = 0, have_args = true, have_ellipsis = false;
 
   /* Compute number of arguments.  */
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 8c70152..518b1eb 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -458,6 +458,11 @@  Wformat-extra-args
 C ObjC C++ ObjC++ Var(warn_format_extra_args) Warning LangEnabledBy(C ObjC C++ ObjC++,Wformat=, warn_format >= 1, 0)
 Warn if passing too many arguments to a function for its format string.
 
+Wformat-length
+C ObjC C++ ObjC++ Warning Alias (Wformat-length=, 1, 0)
+Warn about function calls with format strings that wite past the end
+of the destination region.
+
 Wformat-nonliteral
 C ObjC C++ ObjC++ Var(warn_format_nonliteral) Warning LangEnabledBy(C ObjC C++ ObjC++,Wformat=, warn_format >= 2, 0)
 Warn about format strings that are not literals.
@@ -482,6 +487,11 @@  Wformat=
 C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_format) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall, 1, 0)
 Warn about printf/scanf/strftime/strfmon format string anomalies.
 
+Wformat-length=
+C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_format_length) Warning LangEnabledBy(C ObjC C++ ObjC++,Wformat=, warn_format >= 1, 0)
+Warn about function calls with format strings that wite past the end
+of the destination region.
+
 Wignored-qualifiers
 C C++ Var(warn_ignored_qualifiers) Warning EnabledBy(Wextra)
 Warn whenever type qualifiers are ignored.
diff --git a/gcc/cp/mangle.c b/gcc/cp/mangle.c
index d5b26d6..d350d2d 100644
--- a/gcc/cp/mangle.c
+++ b/gcc/cp/mangle.c
@@ -1622,7 +1622,9 @@  static void
 write_real_cst (const tree value)
 {
   long target_real[4];  /* largest supported float */
-  char buffer[9];       /* eight hex digits in a 32-bit number */
+  /* Buffer for eight hex digits in a 32-bit number but big enough
+     even for 64-bit long to avoid warnings.  */
+  char buffer[17];
   int i, limit, dir;
 
   tree type = TREE_TYPE (value);
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 9a4db38..a12a2ab 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -267,7 +267,8 @@  Objective-C and Objective-C++ Dialects}.
 -Wno-div-by-zero -Wdouble-promotion -Wduplicated-cond @gol
 -Wempty-body  -Wenum-compare -Wno-endif-labels @gol
 -Werror  -Werror=* -Wfatal-errors -Wfloat-equal  -Wformat  -Wformat=2 @gol
--Wno-format-contains-nul -Wno-format-extra-args -Wformat-nonliteral @gol
+-Wno-format-contains-nul -Wno-format-extra-args -Wformat-length=2 @gol
+-Wformat-nonliteral @gol
 -Wformat-security  -Wformat-signedness  -Wformat-y2k -Wframe-address @gol
 -Wframe-larger-than=@var{len} -Wno-free-nonheap-object -Wjump-misses-init @gol
 -Wignored-qualifiers  -Wignored-attributes  -Wincompatible-pointer-types @gol
@@ -3819,6 +3820,81 @@  in the case of @code{scanf} formats, this option suppresses the
 warning if the unused arguments are all pointers, since the Single
 Unix Specification says that such unused arguments are allowed.
 
+@item -Wformat-length
+@itemx -Wformat-length=@var{level}
+@opindex Wformat-length
+@opindex Wno-format-length
+@opindex ffreestanding
+@opindex fno-builtin
+@opindex Wformat-length=
+
+The @option{-Wformat-length} option causes GCC to attempt to detect calls
+to formatting functions such as @code{sprintf} that might overflow the
+destination buffer, or bounded functions like @code{snprintf} that result
+in output truncation.  GCC counts the number of bytes that each format
+string and directive within it writes into the provided buffer and, when
+it detects that more bytes that fit in the destination buffer may be output,
+it emits a warning.  Directives whose arguments have values that can be
+determined at compile-time account for the exact number of bytes they write.
+Directives with arguments whose values cannot be determined are processed
+based on heuristics that depend on the @var{level} argument to the option,
+and on optimization.  The default setting of @var{level} is 1.  Level
+@var{1} employs a conservative approach that warns only about calls that
+most likely overflow the buffer or result in output truncation.  At this
+level, numeric arguments to format directives whose values are unknown
+are assumed to have the value of one, and strings of unknown length are
+assumed to have a length of zero.  Numeric arguments that are known to
+be bounded to a subrange of their type, or string arguments whose output
+is bounded by their directive's precision, are assumed to take on the value
+within the range that results in the most bytes on output.  Level @var{2}
+warns also bout calls that may overflow the destination buffer or result
+in truncation given an argument of sufficient length or magnitude.  At
+this level, unknown numeric arguments are assumed to have the minimum
+representable value for signed types with a precision greater than 1,
+and the maximum representable value otherwise.  Unknown string arguments
+are assumed to be 1 character long.  Enabling optimization will in most
+cases improve the accuracy of the warning, although in some cases it may
+also result in false positives.
+
+For example, at level @var{1}, the call to @code{sprintf} below is diagnosed
+because even with both @var{a} and @var{b} equal to zero, the terminating
+NUL character (@code{'\0'}) appended by the function to the destination
+buffer will be written past its end.  Increasing the size of the buffer by
+a single byte is sufficient to avoid the warning.  At level @var{2}, the call
+is again diagnosed, but this time because with @var{a} equal to a 32-bit
+@code{INT_MIN} the first @code{%i} directive will write some of its digits
+beyond the end of the destination buffer.  To make the call safe regardless
+of the values of the two variables the size of the destination buffer must
+be increased to at least 34 bytes.  GCC includes the minimum size of the
+buffer in an inforational note following the warning.
+
+@smallexample
+void f (int a, int b)
+@{
+  char buf [12];
+  sprintf (buf, "a = %i, b = %i\n", a, b);
+@}
+@end smallexample
+
+An alternative to increasing the size of the destination buffer is to
+constrain the range of formatted values.  The maximum length of string
+arguments can be bounded by specifying the precision in the fortmat
+directive.  When numeric arguments of format directives can be assumed
+to be bounded by less than the precision of their type, choosing
+an appropriate length modifier to the format character will reduce
+the minimum buffer size.  For exampe, if @var{a} and @var{b} above can
+be assumed to be within the precision of the @code{short int} type then
+using either the @code{%hi} format directive or casting the argument to
+@code{short} reduces the maximum required size of the buffer to 24 bytes.
+
+@smallexample
+void f (int a, int b)
+@{
+  char buf [23];
+  sprintf (buf, "a = %hi, b = %i\n", a, (short)b);
+@}
+@end smallexample
+
 @item -Wno-format-zero-length
 @opindex Wno-format-zero-length
 @opindex Wformat-zero-length
diff --git a/gcc/genmatch.c b/gcc/genmatch.c
index 02e945a..e0ac791 100644
--- a/gcc/genmatch.c
+++ b/gcc/genmatch.c
@@ -4051,7 +4051,7 @@  parser::parse_expr ()
   else if (force_capture)
     {
       unsigned num = capture_ids->elements ();
-      char id[8];
+      char id[13];   /* Big enough for a 32-bit UINT_MAX.  */
       bool existed;
       sprintf (id, "__%u", num);
       capture_ids->get_or_insert (xstrdup (id), &existed);
diff --git a/gcc/genmodes.c b/gcc/genmodes.c
index 788031b..79783fd 100644
--- a/gcc/genmodes.c
+++ b/gcc/genmodes.c
@@ -486,7 +486,7 @@  make_vector_modes (enum mode_class cl, unsigned int width,
 {
   struct mode_data *m;
   struct mode_data *v;
-  char buf[8];
+  char buf[12];
   unsigned int ncomponents;
   enum mode_class vclass = vector_class (cl);
 
diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c
new file mode 100644
index 0000000..fa1f0df
--- /dev/null
+++ b/gcc/gimple-ssa-sprintf.c
@@ -0,0 +1,2094 @@ 
+/* Copyright (C) 2016 Free Software Foundation, Inc.
+   Contributed by Martin Sebor <msebor@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "backend.h"
+#include "tree.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "ssa.h"
+#include "gimple-pretty-print.h"
+#include "diagnostic-core.h"
+#include "fold-const.h"
+#include "gimple-iterator.h"
+#include "tree-ssa.h"
+#include "params.h"
+#include "tree-cfg.h"
+#include "calls.h"
+#include "cfgloop.h"
+#include "intl.h"
+
+#include "builtins.h"
+#include "stor-layout.h"
+
+const pass_data pass_data_sprintf_length = {
+  GIMPLE_PASS,        // pass type
+  "sprintf_length",   // pass name
+  OPTGROUP_NONE,      // optinfo_flags
+  TV_NONE,            // tv_id
+  PROP_cfg,           // properties_required
+  0,	              // properties_provided
+  0,	              // properties_destroyed
+  0,	              // properties_start
+  0,	              // properties_finish
+};
+
+class pass_sprintf_length : public gimple_opt_pass
+{
+  bool fold_return_value;
+
+public:
+  pass_sprintf_length (gcc::context *ctxt)
+    : gimple_opt_pass (pass_data_sprintf_length, ctxt),
+    fold_return_value (false)
+  { }
+
+  opt_pass * clone () { return new pass_sprintf_length (m_ctxt); }
+
+  virtual bool gate (function *);
+
+  virtual unsigned int execute (function *);
+
+  void set_pass_param (unsigned int n, bool param)
+    {
+      gcc_assert (n == 0);
+      fold_return_value = param;
+    }
+
+  void handle_gimple_call (gimple *);
+
+  struct call_info;
+  void compute_format_length (const call_info &);
+};
+
+bool
+pass_sprintf_length::gate (function *)
+{
+  /* Run the pass iff -Warn-format-length is specified and either
+     not optimizing and the pass is being invoked early, or when
+     optimizing and the pass is being invoked duriing optimization.  */
+  return 0 < warn_format_length && (0 < optimize) == fold_return_value;
+}
+
+struct format_result
+{
+  /* Number of characters written by the formatting function, exact,
+     minimum and maximum when an exact number cannot be determined.
+     Setting the minimum to a negative value disables all length
+     tracking for the remainder of the format string.
+     Setting either of the other two members disables the exact or
+     maximum length tracking, respectively, but continues to track
+     the maximum.  */
+  int number_chars;
+  int number_chars_min;
+  int number_chars_max;
+
+  /* True when the range given by NUMBER_CHARS_MIN and NUMBER_CHARS_MAX
+     is the output of all directives is determined to be bounded to some
+     subrange of their types or possible lengths, false otherwise.  */
+  bool bounded;
+  bool constant;
+  bool warned;
+
+  /* Location of the format string.  */
+  location_t format_string_loc;
+
+  /* Increment the number of output characters by N.  */
+  void inc_number_chars (int n = 1)
+  {
+    gcc_assert (0 <= n);
+    if (0 <= number_chars)
+      number_chars += n;
+    if (0 <= number_chars_min)
+      number_chars_min += n;
+    if (0 <= number_chars_max)
+      number_chars_max += n;
+  }
+};
+
+static tree
+decl_constant_value (tree decl)
+{
+  if (/* Don't change a variable array bound or initial value to a constant
+	 in a place where a variable is invalid.  Note that DECL_INITIAL
+	 isn't valid for a PARM_DECL.  */
+      current_function_decl != 0
+      && TREE_CODE (decl) != PARM_DECL
+      && !TREE_THIS_VOLATILE (decl)
+      && TREE_READONLY (decl)
+      && DECL_INITIAL (decl) != 0
+      && TREE_CODE (DECL_INITIAL (decl)) != ERROR_MARK
+      /* This is invalid if initial value is not constant.
+	 If it has either a function call, a memory reference,
+	 or a variable, then re-evaluating it could give different results.  */
+      && TREE_CONSTANT (DECL_INITIAL (decl))
+      /* Check for cases where this is sub-optimal, even though valid.  */
+      && TREE_CODE (DECL_INITIAL (decl)) != CONSTRUCTOR)
+    return DECL_INITIAL (decl);
+  return decl;
+}
+
+static const char*
+get_format_string (tree format_tree, location_t *ploc)
+{
+  if (VAR_P (format_tree))
+    {
+      /* Pull out a constant value if the front end didn't.  */
+      format_tree = decl_constant_value (format_tree);
+      STRIP_NOPS (format_tree);
+    }
+
+  if (integer_zerop (format_tree))
+    {
+      // FIXME: Diagnose null format string.
+      return NULL;
+    }
+
+  HOST_WIDE_INT offset = 0;
+
+  if (TREE_CODE (format_tree) == POINTER_PLUS_EXPR)
+    {
+      tree arg0 = TREE_OPERAND (format_tree, 0);
+      tree arg1 = TREE_OPERAND (format_tree, 1);
+      STRIP_NOPS (arg0);
+      STRIP_NOPS (arg1);
+
+      if (TREE_CODE (arg1) != INTEGER_CST)
+	return NULL;
+
+      format_tree = arg0;
+
+      /* POINTER_PLUS_EXPR offsets are to be interpreted signed.  */
+      if (!cst_and_fits_in_hwi (arg1))
+	return NULL;
+
+      offset = int_cst_value (arg1);
+    }
+
+  if (TREE_CODE (format_tree) != ADDR_EXPR)
+    return NULL;
+
+  *ploc = EXPR_LOC_OR_LOC (format_tree, input_location);
+
+  format_tree = TREE_OPERAND (format_tree, 0);
+
+  if (TREE_CODE (format_tree) == ARRAY_REF
+      && tree_fits_shwi_p (TREE_OPERAND (format_tree, 1))
+      && (offset += tree_to_shwi (TREE_OPERAND (format_tree, 1))) >= 0)
+    format_tree = TREE_OPERAND (format_tree, 0);
+
+  if (offset < 0)
+    return NULL;
+
+  tree array_init;
+  tree array_size = NULL_TREE;
+
+  if (VAR_P (format_tree)
+      && TREE_CODE (TREE_TYPE (format_tree)) == ARRAY_TYPE
+      && (array_init = decl_constant_value (format_tree)) != format_tree
+      && TREE_CODE (array_init) == STRING_CST)
+    {
+      /* Extract the string constant initializer.  Note that this may include
+	 a trailing NUL character that is not in the array (e.g.
+	 const char a[3] = "foo";).  */
+      array_size = DECL_SIZE_UNIT (format_tree);
+      format_tree = array_init;
+    }
+
+  if (TREE_CODE (format_tree) != STRING_CST)
+    return NULL;
+
+  if (TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (format_tree))) != char_type_node)
+    {
+      /* Wide format string.  */
+      return NULL;
+    }
+
+  const char *format_chars = TREE_STRING_POINTER (format_tree);
+  unsigned format_length = TREE_STRING_LENGTH (format_tree);
+
+  if (array_size)
+    {
+      /* Variable length arrays can't be initialized.  */
+      gcc_assert (TREE_CODE (array_size) == INTEGER_CST);
+
+      if (tree_fits_shwi_p (array_size))
+	{
+	  HOST_WIDE_INT array_size_value = tree_to_shwi (array_size);
+	  if (array_size_value > 0
+	      && array_size_value == (int) array_size_value
+	      && format_length > array_size_value)
+	    format_length = array_size_value;
+	}
+    }
+  if (offset)
+    {
+      if (offset >= format_length)
+	return NULL;
+
+      format_chars += offset;
+      format_length -= offset;
+    }
+
+  if (format_length < 1 || format_chars[--format_length] != 0)
+    {
+      /* Unterminated format string.  */
+      return NULL;
+    }
+
+  return format_chars;
+}
+
+/* Given a string S of length LINE_WIDTH, find the visual column
+   corresponding to OFFSET bytes.   */
+
+static unsigned int
+location_column_from_byte_offset (const char *s, int line_width,
+				  unsigned int offset)
+{
+  const char * c = s;
+  if (*c != '"')
+    return 0;
+
+  c++, offset--;
+  while (offset > 0)
+    {
+      if (c - s >= line_width)
+	return 0;
+
+      switch (*c)
+	{
+	case '\\':
+	  c++;
+	  if (c - s >= line_width)
+	    return 0;
+	  switch (*c)
+	    {
+	    case '\\': case '\'': case '"': case '?':
+	    case '(': case '{': case '[': case '%':
+	    case 'a': case 'b': case 'f': case 'n':
+	    case 'r': case 't': case 'v':
+	    case 'e': case 'E':
+	      c++, offset--;
+	      break;
+
+	    default:
+	      return 0;
+	    }
+	  break;
+
+	case '"':
+	  /* We found the end of the string too early.  */
+	  return 0;
+
+	default:
+	  c++, offset--;
+	  break;
+	}
+    }
+  return c - s;
+}
+
+/* Return a location that encodes the same location as LOC but shifted
+   by OFFSET bytes.  */
+
+static location_t
+location_from_offset (location_t loc, int offset)
+{
+  gcc_checking_assert (offset >= 0);
+  if (linemap_location_from_macro_expansion_p (line_table, loc)
+      || offset < 0)
+    return loc;
+
+  expanded_location s = expand_location_to_spelling_point (loc);
+  int line_width;
+  const char *line = location_get_source_line (s.file, s.line, &line_width);
+  if (line == NULL)
+    return loc;
+  line += s.column - 1 ;
+  line_width -= s.column - 1;
+  unsigned int column =
+    location_column_from_byte_offset (line, line_width, (unsigned) offset);
+
+  return linemap_position_for_loc_and_offset (line_table, loc, column);
+}
+
+/* Return a location that encodes the same location as LOC but shifted
+   by OFFSET bytes and ending LENGTH bytes including the caret.  For
+   example, to create a location for the %i directive embedded in the
+   format string below from its location, call the function with OFFSET
+   = 5 and LENGTH 3 to end up with the following:
+     sprintf ("abc-%3i-def", ...)
+                   ^~~
+*/
+
+static location_t
+location_from_offset (location_t loc, int offset, int length)
+{
+  location_t beg = location_from_offset (loc, offset);
+  if (length == 0)
+    return beg;
+
+  location_t end = location_from_offset (loc, offset + length - 1);
+  return make_location (beg, beg, end);
+}
+
+/* Format length modifiers.  */
+
+enum format_lengths
+{
+  FMT_LEN_none,
+  FMT_LEN_hh,    // char argument
+  FMT_LEN_h,     // short
+  FMT_LEN_l,     // long
+  FMT_LEN_ll,    // long long
+  FMT_LEN_L,     // long double (and GNU long long)
+  FMT_LEN_z,     // size_t
+  FMT_LEN_t,     // ptrdiff_t
+  FMT_LEN_j      // intmax_t
+};
+
+/* Description of the result of conversion either of a single directive
+   or the whole format string.  */
+
+struct fmtresult
+{
+  /* The range a directive's argument is in.  */
+  tree argmin, argmax;
+  /* The minimum and maximum number of bytes that a directive
+     results in on output for an argument in the range above.  */
+  int min, max;
+  /* True when the range is the result of an argument determined
+     to be bounded to a subrange of its type, false otherwise.  */
+  bool bounded;
+  bool constant;
+};
+
+/* Description of a conversion specification.  */
+
+struct conversion_spec
+{
+  /* A bitmap of flags, one for each character.  */
+  int flags[256 / sizeof (int)];
+  int width;             /* Numeric width.  */
+  int precision;         /* Numeric precision.  */
+
+  tree star_width;       /* Width specified via the '*' character.  */
+  tree star_precision;   /* Precision specified via the asterisk.  */
+
+  format_lengths modifier;   /* Length modifier.  */
+  char specifier;            /* Format specifier character.  */
+
+  unsigned have_width: 1;       /* Numeric width was given.  */
+  unsigned have_precision: 1;   /* Numeric precision was given.  */
+
+  fmtresult  (*fmtfunc) (const conversion_spec &, tree);
+
+  /* Return True when a the format flag CHR has been used.  */
+  bool get_flag (char chr) const
+  {
+    unsigned char c = chr & 0xff;
+    return flags[c / (CHAR_BIT * sizeof *flags)]
+      & (1 << (c % (CHAR_BIT * sizeof *flags)));
+  }
+
+  /* Make a record of the format flag CHR having been used.  */
+  void set_flag (char chr)
+  {
+    unsigned char c = chr & 0xff;
+    flags[c / (CHAR_BIT * sizeof *flags)]
+      |= (1 << (c % (CHAR_BIT * sizeof *flags)));
+  }
+};
+
+/* Return the logarithm of X in BASE.  */
+
+static int
+ilog (unsigned HOST_WIDE_INT x, int base)
+{
+  int res = 0;
+  do {
+    ++res;
+    x /= base;
+  } while (x);
+  return res;
+}
+
+/* Return the number of bytes resulting from converting into a string
+   the INTEGER_CST tree node X in BASE.  PLUS indicates whether 1 for
+   a plus sign should be added for positive numbers, and PREFIX whether
+   the length of an octal ('O') or hexadecimal ('0x') prefix should be
+   added for  nonzero numbers.  Return -1 if X cannot be represented.  */
+
+static int
+tree_digits (tree x, int base, bool plus, bool prefix)
+{
+  unsigned HOST_WIDE_INT absval;
+
+  int res;
+
+  if (TYPE_UNSIGNED (TREE_TYPE (x)))
+    {
+      if (tree_fits_uhwi_p (x))
+	{
+	  absval = tree_to_uhwi (x);
+	  res = plus;
+	}
+      else
+	return -1;
+    }
+  else
+    {
+      if (tree_fits_shwi_p (x))
+	{
+	  HOST_WIDE_INT i = tree_to_shwi (x);
+	  if (i < 0)
+	    {
+	      absval = -i;
+	      res = 1;
+	    }
+	  else
+	    {
+	      absval = i;
+	      res = plus;
+	    }
+	}
+      else
+	return -1;
+    }
+
+  res += ilog (absval, base);
+
+  if (prefix && absval)
+    {
+      if (base == 8)
+	res += 1;
+      else if (base == 16)
+	res += 2;
+    }
+
+  return res;
+}
+
+/* Given the formatting result described by RES, return the number
+   of bytes remaining in the destination buffer whose size is OBJSIZE.  */
+
+static inline size_t
+bytes_remaining (size_t               navail,
+		 const format_result &res)
+{
+  if (1 < warn_format_length || res.bounded)
+    {
+      /* 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 (0 <= res.number_chars_max)
+	navail -= res.number_chars_max;
+      else if (0 <= res.number_chars)
+	navail -= res.number_chars;
+      else if (0 <= res.number_chars_min)
+	navail -= res.number_chars_min;
+    }
+  else
+    {
+      /* At level 1 use the smaller of the byte counters to compute
+	 the result.  */
+      if (0 <= res.number_chars)
+	navail -= res.number_chars;
+      else if (0 <= res.number_chars_min)
+	navail -= res.number_chars_min;
+      else if (0 <= res.number_chars_max)
+	navail -= res.number_chars_max;
+    }
+
+  return navail;
+}
+
+
+struct pass_sprintf_length::call_info
+{
+  /* Function call statement.  */
+  gimple *callstmt;
+
+  /* Function called.  */
+  tree func;
+
+  /* Called built-in function code.  */
+  built_in_function fncode;
+
+  /* Format argument.  */
+  tree format;
+  const char *fmtstr;
+
+  /* The location of the format argument.  */
+  location_t fmtloc;
+
+  /* The destination size specified for bounded functions such
+     as snprintf, -1 for others.  */
+  // unsigned HOST_WIDE_INT size;
+
+  /* The destination object size for __builtin___xxx_chk functions
+     typically determined by __builtin_object_size, or -1 if unknown.  */
+  unsigned HOST_WIDE_INT objsize;
+
+  /* Number of the first variable argument.  */
+  unsigned HOST_WIDE_INT argidx;
+
+  bool bounded;
+};
+
+/* Return the result of formatting the '%%' directive.  */
+
+static fmtresult
+format_percent (const conversion_spec &, tree)
+{
+  fmtresult res;
+  res.argmin = res.argmax = NULL_TREE;
+  res.min = res.max = 1;
+  res.bounded = res.constant = true;
+  return res;
+}
+
+
+/* Ugh.  Compute intmax_type_node and uintmax_type_node the same way
+   lto/lto-lang.c does it.  */
+
+static void
+build_intmax_type_nodes (tree *pintmax, tree *puintmax)
+{
+  if (strcmp (SIZE_TYPE, "unsigned int") == 0)
+    {
+      *pintmax = integer_type_node;
+      *puintmax = unsigned_type_node;
+    }
+  else if (strcmp (SIZE_TYPE, "long unsigned int") == 0)
+    {
+      *pintmax = long_integer_type_node;
+      *puintmax = long_unsigned_type_node;
+    }
+  else if (strcmp (SIZE_TYPE, "long long unsigned int") == 0)
+    {
+      *pintmax = long_long_integer_type_node;
+      *puintmax = long_long_unsigned_type_node;
+    }
+  else
+    {
+      for (int i = 0; i < NUM_INT_N_ENTS; i++)
+        if (int_n_enabled_p[i])
+          {
+            char name[50];
+            sprintf (name, "__int%d unsigned", int_n_data[i].bitsize);
+
+            if (strcmp (name, SIZE_TYPE) == 0)
+              {
+                *pintmax = int_n_trees[i].signed_type;
+                *puintmax = int_n_trees[i].unsigned_type;
+              }
+          }
+    }
+}
+
+/* Return a range representing the minimum and maximum number of bytes
+   that the conversion specification SPEC will write on output for the
+   integer argument ARG when non-null.  ARG may be null (for vararg
+   functions).  */
+
+static fmtresult
+format_integer (const conversion_spec &spec, tree arg)
+{
+  /* These are available as macros in the C and C++ front ends but,
+     sadly, not here.  */
+  static tree intmax_type_node;
+  static tree uintmax_type_node;
+
+  /* Initialize the intmax nodes above the first time through here.  */
+  if (!intmax_type_node)
+    build_intmax_type_nodes (&intmax_type_node, &uintmax_type_node);
+
+  /* Set WIDTH and PRECISION to either the values in the format
+     specification or to zero.  */
+  int width = spec.have_width ? spec.width : 0;
+  int prec = spec.have_precision ? spec.precision : 0;
+
+  if (spec.star_width)
+    width = (TREE_CODE (spec.star_width) == INTEGER_CST
+	     ? tree_to_shwi (spec.star_width) : 0);
+
+  if (spec.star_precision)
+    prec = (TREE_CODE (spec.star_precision) == INTEGER_CST
+	    ? tree_to_shwi (spec.star_precision) : 0);
+
+  bool sign = spec.specifier == 'd' || spec.specifier == 'i';
+
+  /* The type of the "formal" argument expected by the directive.  */
+  tree dirtype = NULL_TREE;
+
+  /* Determine the expected type of the argument from the length
+     midifier.  */
+  switch (spec.modifier)
+    {
+    case FMT_LEN_none:
+      if (spec.specifier == 'p')
+	dirtype = ptr_type_node;
+      else
+	dirtype = sign ? integer_type_node : unsigned_type_node;
+      break;
+
+    case FMT_LEN_h:
+      dirtype = sign ? short_integer_type_node : short_unsigned_type_node;
+      break;
+
+    case FMT_LEN_hh:
+      dirtype = sign ? signed_char_type_node : unsigned_char_type_node;
+      break;
+
+    case FMT_LEN_l:
+      dirtype = sign ? long_integer_type_node : long_unsigned_type_node;
+      break;
+
+    case FMT_LEN_L:
+    case FMT_LEN_ll:
+      dirtype = sign ? long_integer_type_node : long_unsigned_type_node;
+      break;
+
+    case FMT_LEN_z:
+      dirtype = sign ? ptrdiff_type_node : size_type_node;
+      break;
+
+    case FMT_LEN_t:
+      dirtype = sign ? ptrdiff_type_node : size_type_node;
+      break;
+
+    case FMT_LEN_j:
+      dirtype = sign ? intmax_type_node : uintmax_type_node;
+      break;
+
+    default:
+      gcc_unreachable ();
+    }
+
+  /* The type of the argument to the directive, either deduced from
+     the actual non-constant argument if one is known, or from
+     the directive itself when none has been provided because it's
+     a va_list.  */
+  tree argtype = NULL_TREE;
+
+  if (!arg)
+    {
+      /* When the argument has not been provided, use the type of
+	 the directive's argument as an approximation.  This will
+	 result in false positives for directives like %i with
+	 arguments with smaller precision (such as short or char).  */
+      argtype = dirtype;
+    }
+  else if (TREE_CODE (arg) == INTEGER_CST)
+    {
+      /* The minimum and maximum number of bytes produced by
+	 the directive.  */
+      fmtresult res = fmtresult ();
+
+      /* When a constant argument has been provided use its value
+	 rather than type to determine the length of the output.  */
+      res.bounded = true;
+      res.constant = true;
+
+      /* Base to format the number in.  */
+      int base = 10;
+
+      /* True when a signed conversion is preceded by a sign or space.  */
+      bool maybesign = false;
+
+      /* True when a conversion is preceded by a prefix indicating the base
+	 of the argument (octal or hexadecimal).  */
+      bool maybebase = false;
+
+      switch (spec.specifier)
+	{
+	case 'd':
+	case 'i':
+	  /* Space is only effective for signed conversions.  */
+	  maybesign = spec.get_flag (' ');
+	case 'u':
+	  break;
+	case 'o':
+	  base = 8;
+	  break;
+
+	case 'p':
+#if TARGET_AIX
+	  /* AIX "%p" is the same as "%x", with '#' being ignored.  */
+#elif defined OPTION_GLIBC
+	  /* GLIBC "%p" is the same as "%#x" except that null pointers
+	     convert to "(nil)".  */
+	  if (integer_zerop (arg))
+	    {
+	      res.min = res.max = 5;
+	      res.bounded = true;
+	      return res;
+	    }
+	  maybebase = true;
+
+	  /* GLIBC also respects the space flag.  */
+	  maybesign = spec.get_flag (' ');
+
+#elif TARGET_SOLARIS
+	  /* Solaris "%p" is the same as "%x" with the '#' flag having
+	     the same function.  */
+	  maybebase = spec.get_flag ('#');
+#else
+	  /* Conservatively assume that all other implementations format
+	     pointers in hexadecimal with no base prefix at a minimum,
+	     and with it as a maximum.  */
+	  res.min = tree_digits (arg, 16, false, false);
+	  res.max = res.min + 2;
+	  res.bounded = false;
+	  return res;
+#endif
+	  base = 16;
+	  break;
+
+	case 'X':
+	case 'x':
+	  base = 16;
+	  break;
+	default:
+	  gcc_unreachable ();
+	}
+
+      /* Convert the argument to the type of the directive.  */
+      arg = fold_convert (dirtype, arg);
+
+      maybesign |= spec.get_flag ('+');
+      maybebase |= spec.get_flag ('#');
+      int len = tree_digits (arg, base, maybesign, maybebase);
+
+      if (len < prec)
+	len = prec;
+
+      if (len < width)
+	len = width;
+
+      res.max = len;
+      res.min = res.max;
+      res.bounded = true;
+
+      return res;
+    }
+  else if (TREE_CODE (TREE_TYPE (arg)) == INTEGER_TYPE
+	   || TREE_CODE (TREE_TYPE (arg)) == POINTER_TYPE)
+    {
+      /* Determine the type of the provided non-constant argument.  */
+      if (TREE_CODE (arg) == NOP_EXPR)
+	arg = TREE_OPERAND (arg, 0);
+      else if (TREE_CODE (arg) == CONVERT_EXPR)
+	arg = TREE_OPERAND (arg, 0);
+      if (TREE_CODE (arg) == COMPONENT_REF)
+	arg = TREE_OPERAND (arg, 1);
+
+      argtype = TREE_TYPE (arg);
+    }
+  else
+    {
+      /* Don't bother with invalid arguments since they likely would
+	 have already been diagnosed, and disable any further checking
+	 of the format string by returning [-1, -1].  */
+      fmtresult res = fmtresult ();
+      res.min = res.max = -1;
+      return res;
+    }
+
+  fmtresult res = fmtresult ();
+
+  /* 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.  */
+  tree argmin = NULL_TREE;
+  tree argmax = NULL_TREE;
+
+  if (arg && TREE_CODE (arg) == SSA_NAME
+      && TREE_CODE (argtype) == INTEGER_TYPE)
+    {
+      /* Try to determine the range of values of the integer argument
+	 (range information is not available for pointers).  */
+      wide_int min, max;
+      enum value_range_type range_type = get_range_info (arg, &min, &max);
+      if (range_type == VR_RANGE)
+	{
+	  res.argmin = build_int_cst (argtype, wi::fits_uhwi_p (min)
+				      ? min.to_uhwi () : min.to_shwi ());
+	  res.argmax = build_int_cst (argtype, wi::fits_uhwi_p (max)
+				      ? max.to_uhwi () : max.to_shwi ());
+
+	  /* For a range with a negative lower bound and a non-negative
+	     upper bound, use one to determine the minimum number of bytes
+	     on output and whichever of the two bounds that results in
+	     the larger number of bytes on output for the upper bound.
+	     For example, for arg in the range of [-3, 123], use 123 as
+	     the upper bound for %i but -3 for %u.  */
+	  if (wi::neg_p (min) && !wi::neg_p (max))
+	    {
+	      argmin = build_int_cst (argtype, wi::fits_uhwi_p (min)
+				      ? min.to_uhwi () : min.to_shwi ());
+
+	      argmax = build_int_cst (argtype, wi::fits_uhwi_p (max)
+				      ? max.to_uhwi () : max.to_shwi ());
+
+	      int minbytes = format_integer (spec, res.argmin).min;
+	      int maxbytes = format_integer (spec, res.argmax).max;
+	      if (maxbytes < minbytes)
+		argmax = res.argmin;
+
+	      argmin = integer_zero_node;
+	    }
+	  else
+	    {
+	      argmin = res.argmin;
+	      argmax = res.argmax;
+	    }
+
+	  /* The argument is bounded by the range of values determined
+	     by the Value Range Propagationa.  */
+	  res.bounded = true;
+	}
+      else if (range_type == VR_ANTI_RANGE)
+	{
+	  /* Handle anti-ranges if/when bug 71690 is ever resolved.  */
+	}
+      else if (range_type == VR_VARYING)
+	{
+	  /* The argument here may be the result of promoting
+	     the actual argument to int.  Try to determine the
+	     type of the actual argument before promotion and
+	     narrow down its range that way.  */
+	  gimple *def = SSA_NAME_DEF_STMT (arg);
+	  if (gimple_code (def) == GIMPLE_ASSIGN)
+	    {
+	      tree_code code = gimple_assign_rhs_code (def);
+	      if (code == NOP_EXPR)
+		argtype = TREE_TYPE (gimple_assign_rhs1 (def));
+	    }
+	}
+    }
+
+  if (!argmin)
+    {
+      /* For an unknown argument (e.g., one passed to a vararg
+	 function) 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.  */
+      argmin = build_int_cst (argtype, 1);
+
+      int typeprec = TYPE_PRECISION (dirtype);
+      int argprec = TYPE_PRECISION (argtype);
+
+      if (argprec < typeprec || POINTER_TYPE_P (argtype))
+	{
+	  if (TYPE_UNSIGNED (argtype))
+	    argmax = build_all_ones_cst (argtype);
+	  else
+	    argmax = fold_build2 (LSHIFT_EXPR, argtype, integer_one_node,
+				  build_int_cst (integer_type_node,
+						 argprec - 1));
+	}
+      else
+	{
+	  argmax = fold_build2 (LSHIFT_EXPR, dirtype, integer_one_node,
+				build_int_cst (integer_type_node,
+					       typeprec - 1));
+	}
+      res.argmin = argmin;
+      res.argmax = argmax;
+    }
+
+  /* Recursively compute the minimum and maximum from the known range,
+     taking care to swap them if the lower bound results in longer
+     output than the upper bound (e.g., in the range [-1, 0].  */
+  res.min = format_integer (spec, argmin).min;
+  res.max = format_integer (spec, argmax).max;
+  if (res.max < res.min)
+    {
+      int tmp = res.max;
+      res.max = res.min;
+      res.min = tmp;
+    }
+  return res;
+}
+
+/* Return a range representing the minimum and maximum number of bytes
+   that the conversion specification SPEC will write on output for the
+   floating argument ARG.  */
+
+static fmtresult
+format_floating (const conversion_spec &spec, tree arg)
+{
+  /* Set WIDTH and PRECISION to either the values in the format
+     specification or to zero.  */
+  int width = spec.have_width ? spec.width : 0;
+  int prec = spec.have_precision ? spec.precision : -1;
+
+  if (spec.star_width)
+    width = (TREE_CODE (spec.star_width) == INTEGER_CST)
+            ? tree_to_shwi (spec.star_width) : 0;
+
+  if (spec.star_precision)
+    prec = (TREE_CODE (spec.star_precision) == INTEGER_CST)
+            ? tree_to_shwi (spec.star_precision) : 0;
+
+  tree type = arg ? TREE_TYPE (arg) : NULL_TREE;
+
+  switch (spec.modifier)
+    {
+    case FMT_LEN_none:
+      if (!type)
+	type = double_type_node;
+      break;
+
+    case FMT_LEN_L:
+    case FMT_LEN_ll:
+      if (!type)
+	type = long_double_type_node;
+      break;
+
+    default:
+      gcc_unreachable ();
+    }
+
+  /* The minimum and maximum number of bytes produced by the directive.  */
+  fmtresult res = fmtresult ();
+
+  res.constant = false;
+
+  /* Number of exponent digits or -1 when unknown.  */
+  int expdigs = -1;
+  /* 1 when ARG < 0, 0 when ARG >= 0, -1 when unknown.  */
+  int negative = -1;
+  /* Log10 of EXPDIGS.  */
+  int logexpdigs = 2;
+
+  const double log10_2 = .30102999566398119521;
+
+  if (arg && TREE_CODE (arg) == REAL_CST)
+    {
+      expdigs = real_exponent (TREE_REAL_CST_PTR (arg)) * log10_2;
+      negative = real_isneg (TREE_REAL_CST_PTR (arg));
+      logexpdigs = ilog (expdigs, 10);
+    }
+  else if (REAL_MODE_FORMAT (TYPE_MODE (type))->b == 2)
+    {
+      /* Compute T_MAX_EXP for base 2.  */
+      expdigs = REAL_MODE_FORMAT (TYPE_MODE (type))->emax * log10_2;
+    }
+
+
+  switch (spec.specifier)
+    {
+    case 'A':
+    case 'a':
+      /* The minimum output is "0x.p+0".  */
+      res.min = 6 + (0 < prec ? prec : 0);
+      /* FIXME: Figure out the maximum.  */
+      res.max = -1;
+      if (res.min < width)
+	res.min = width;
+      break;
+
+    case 'E':
+    case 'e':
+      /* The minimum output is "[-+]1.234567e+00" for an IEEE double
+	 regardless of the value of the actual argument. */
+      res.min = ((0 < negative || spec.get_flag ('+') || spec.get_flag (' '))
+		 + 1 /* unit */ + (prec < 0 ? 7 : prec ? prec + 1 : 0)
+		 + 2 /* e+ */ + (logexpdigs < 2 ? 2 : logexpdigs));
+      /* The maximum output is "-1.234567e+123" for a double and one
+	 more byte for a large exponent for a long louble.  */
+      res.max = negative < 0 ? res.min + 2 + (spec.get_flag ('L')) : res.min;
+      if (res.min < width)
+	res.min = width;
+      if (res.max < width)
+	res.max = width;
+      break;
+
+    case 'F':
+    case 'f':
+      /* The minimum output is "1.234567" regardless of the value
+	 of the actual argument. */
+      res.min = 2 + (prec < 0 ? 6 : prec);
+      /* The maximum depends on the magnitude of the value but it's
+	 at most 316 bytes for double and 4940 for long double, plus
+	 precision if non-negative, or 6.  */
+      res.max = expdigs + (prec < 0 ? 6 : prec ? prec + 1 : 0);
+      break;
+
+    case 'G':
+    case 'g':
+      /* Treat this the same as '%F' for now even though that's
+	 inaccurate.  */
+      res.min = 2 + (prec < 0 ? 6 : prec);
+      res.max = ((spec.get_flag ('L') ? 4934 : 310)
+		 + (prec < 0 ? 6 : prec ? prec + 1 : 0));
+      break;
+
+    default:
+      gcc_unreachable ();
+    }
+
+  /* The argument is only considered bounded when the range of output
+     bytes is exact.  */
+  res.bounded = res.min == res.max;
+  return res;
+}
+
+/* Return a FMTRESULT struct set to the lengths of the shortest and longest
+   strings referenced by the expression STR, or (-1, -1) when not known.
+   Used by the format_string function below.  */
+
+static fmtresult
+get_string_length (tree str)
+{
+  if (!str)
+    {
+      fmtresult res;
+      res.min = -1;
+      res.max = -1;
+      res.bounded = false;
+      res.constant = false;
+      return res;
+    }
+
+  if (tree slen = c_strlen (str, 1))
+    {
+      /* Simply return the length of the string.  */
+      fmtresult res;
+      res.min = res.max = tree_to_shwi (slen);
+      res.bounded = true;
+      res.constant = true;
+      return res;
+    }
+
+  if (TREE_CODE (str) == ADDR_EXPR)
+    return get_string_length (TREE_OPERAND (str, 0));
+
+  if (TREE_CODE (str) == COMPONENT_REF
+      && TREE_CODE (TREE_TYPE (TREE_OPERAND (str, 1))) == ARRAY_TYPE)
+    {
+      /* Use the type of the member array to determine the upper bound
+	 on the length of the array.  This may be overly optimistic if
+	 the array itself isn't NUL-terminated and the caller relies
+	 on the subsequent member to contain the NUL.  */
+      fmtresult res;
+      if (tree arraysize = TYPE_SIZE_UNIT (TREE_TYPE (TREE_OPERAND (str, 1))))
+	{
+	  res.min = 0;
+	  res.max = tree_to_uhwi (arraysize) - 1;
+	}
+      else
+	  res.min = res.max = -1;
+
+      /* Using the type of the character array to determine the maximum
+	 length of output is not considered bounded.  */
+      res.bounded = false;
+      res.constant = false;
+      return res;
+    }
+
+  if (TREE_CODE (str) == SSA_NAME)
+    {
+      gimple *def = SSA_NAME_DEF_STMT (str);
+      enum gimple_code code = gimple_code (def);
+
+      if (code == GIMPLE_ASSIGN)
+	{
+	  tree rhs = gimple_assign_rhs1 (def);
+	  return get_string_length (rhs);
+	}
+      else if (code == GIMPLE_PHI)
+	{
+	  /* Try to determine the longest and shortest string the argument
+	     refers to in an attempt to handle buffer overflow in common
+	     cases such as:
+	       char d [8];
+	       sprintf (d, "[%s]", i ? "abcdef", "xyz");
+	  */
+	  fmtresult res = fmtresult ();
+	  res.min = INT_MAX;
+	  res.max = 0;
+
+	  for (unsigned i = 0; i < gimple_phi_num_args (def); ++i)
+	  {
+	    tree arg = gimple_phi_arg (def, i)->def;
+	    fmtresult tmp = get_string_length (arg);
+	    if (tmp.min < res.min && 0 <= tmp.min)
+	      res.min = tmp.min;
+	    if (res.max < tmp.max || tmp.max < 0)
+	      res.max = tmp.max;
+
+	    res.bounded |= tmp.bounded;
+	  }
+
+	  if (res.min == INT_MAX)
+	    res.min = -1;
+
+	  res.constant = false;
+	  return res;
+	}
+    }
+
+  return get_string_length (NULL_TREE);
+}
+
+/* Return the minimum and maximum number of characters formatted
+   by the '%c' and '%s' format directives and ther wide character
+   forms.  */
+
+static fmtresult
+format_string (const conversion_spec &spec, tree arg)
+{
+  int width = spec.have_width ? spec.width : 0;
+  int prec = spec.have_precision ? spec.precision : -1;
+
+  if (spec.star_width)
+    width = (TREE_CODE (spec.star_width) == INTEGER_CST)
+            ? tree_to_shwi (spec.star_width) : 0;
+
+  if (spec.star_precision)
+    prec = (TREE_CODE (spec.star_precision) == INTEGER_CST)
+           ? tree_to_shwi (spec.star_precision) : -1;
+
+  fmtresult res = fmtresult ();
+
+  /* The argument is likely unbounded (i.e., its length is likely
+     unknown.  */
+  res.bounded = false;
+  res.constant = false;
+
+  /* The number of bytes formatted.  This applies to both '%s' and
+     '%ls' where precision and width are in converted characters
+     (i.e., bytes).  */
+  int nbytes;
+
+  if (spec.specifier == 'c')
+    {
+      if (spec.modifier == FMT_LEN_l)
+	{
+	  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.min = 0 < width ? width : 1 < warn_format_length ? nul < 1: !nul;
+	  res.max = -1;
+	  return res;
+	}
+
+      /* A plain '%c' directive.  */
+      nbytes = 1;
+      res.constant = arg && TREE_CODE (arg) == INTEGER_CST;
+    }
+  else
+    {
+      fmtresult slen = get_string_length (arg);
+      if (slen.constant)
+	{
+	  gcc_checking_assert (slen.min == slen.max);
+
+	  /* A '%s' directive with a string argument with constant length.  */
+	  nbytes = slen.min;
+	  if (0 <= prec && prec < nbytes)
+	    nbytes = prec;
+
+	  if (spec.modifier == FMT_LEN_l)
+	    {
+	      /* For a '%ls' directive the minimum number of bytes is
+		 the greater of WIDTH and the string length, and the
+		 maximum is either PRECISION when specified or
+		 MB_CUR_MAX * length, which is unknown, so set it
+		 to -1.  */
+	      res.min = nbytes < width ? width : nbytes;
+	      /* 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.max = 0 <= prec ? prec : -1;
+
+	      res.bounded = -1 < res.max;
+	      return res;
+	    }
+
+	  res.constant = true;
+	}
+      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 (prec < slen.min || slen.min < 0)
+		slen.min = prec;
+	      if (prec < slen.max || slen.max < 0)
+		slen.max = prec;
+	    }
+	  else if (slen.min < 0)
+	    slen.min = width ? width : 1 < warn_format_length;
+
+	  res.min = slen.min;
+	  res.max = slen.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;
+	  return res;
+	}
+    }
+
+  if (nbytes < width)
+    nbytes = width;
+
+  res.min = res.max = nbytes;
+
+  /* The length is exact.  */
+  res.bounded = true;
+
+  return res;
+}
+
+static void
+compute_format_length (const pass_sprintf_length::call_info &info,
+		       format_result                        *res,
+		       const char                           *cvtbeg,
+		       size_t                               cvtlen,
+		       size_t                               offset,
+		       const conversion_spec                &spec,
+		       tree                                 arg)
+{
+  /* Create a location for the whole directive from the % to the format
+     specifier.  */
+  location_t dirloc = location_from_offset (info.fmtloc, offset + 1, cvtlen);
+
+  /* Bail when there is no function to compute the output length,
+     or when the size of the object is too big (i.e., unknown)
+     or when minimum length checking has been disabled.   */
+  if (!spec.fmtfunc
+      || HOST_WIDE_INT_MAX <= info.objsize
+      || res->number_chars_min == -1)
+    return;
+
+  /* Compute the (approximate) length of the formatted output.  */
+  fmtresult fmtres = spec.fmtfunc (spec, arg);
+
+  /* The overall result is bounded only if the output of every
+     directive is exact or bounded.  */
+  res->bounded = res->bounded && fmtres.bounded;
+  res->constant = res->constant && fmtres.constant;
+
+    if (fmtres.max < 0)
+    {
+      /* Disable exact and maximum length checking after a failure
+	 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 = -1;
+      res->number_chars = -1;
+    }
+
+  if (fmtres.min < 0)
+    {
+      /* Disable exact length checking after a failure to determine
+	 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 = -1;
+      return;
+    }
+
+  /* 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 = bytes_remaining (info.objsize, *res);
+
+  /* The destination will have already overflowed if the number of
+     bytes has wrapped around zero.  */
+  bool overflowed = HOST_WIDE_INT_MAX <= navail;
+
+  if (fmtres.min < fmtres.max)
+    {
+      /* The result is a range (i.e., it's inexact).  */
+      if (!overflowed)
+	{
+	  bool warned = false;
+
+	  if (navail < (size_t)fmtres.min)
+	    {
+	      if (fmtres.min == fmtres.max)
+		{
+		  const char* fmtstr
+		    = (info.bounded
+		       ? G_("%<%.*s%> directive output truncated writing "
+			    "%i bytes into a region of size %wu")
+		       : G_("%<%.*s%> directive writing %i bytes "
+			    "into a region of size %wu"));
+		  warned = warning_at (dirloc, OPT_Wformat_length_, fmtstr,
+				       (int)cvtlen, cvtbeg, fmtres.min,
+				       navail);
+		}
+	      else
+		{
+		  const char* fmtstr
+		    = (info.bounded
+		       ? G_("%<%.*s%> directive output truncated writing "
+			    "between %i and %i bytes into a region of size %wu")
+		       : G_("%<%.*s%> directive writing between %i and %i bytes "
+			    "into a region of size %wu"));
+		  warned = warning_at (dirloc, OPT_Wformat_length_, fmtstr,
+				       (int)cvtlen, cvtbeg,
+				       fmtres.min, fmtres.max, navail);
+		}
+	    }
+	  else if (navail < (unsigned)fmtres.max
+		   && (fmtres.bounded || 1 < warn_format_length))
+	    {
+	      const char* fmtstr
+		= (info.bounded
+		   ? G_("%<%.*s%> directive output may be truncated writing "
+			"between %i and %i bytes into a region of size %wu")
+		   : G_("%<%.*s%> directive writing between %i and %i bytes "
+			"into a region of size %wu"));
+	      warned = warning_at (dirloc, OPT_Wformat_length_, fmtstr,
+				   (int)cvtlen, cvtbeg,
+				   fmtres.min, fmtres.max, navail);
+	    }
+
+	  res->warned |= warned;
+
+	  if (warned && fmtres.argmin)
+	    {
+	      if (fmtres.argmin == fmtres.argmax)
+		inform (dirloc, "directive argument %qE", fmtres.argmin);
+	      else if (fmtres.bounded)
+		inform (dirloc, "directive argument in the range [%qE, %qE]",
+			fmtres.argmin, fmtres.argmax);
+	      else
+		inform (dirloc,
+			"using the range [%qE, %qE] for directive argument",
+			fmtres.argmin, fmtres.argmax);
+	    }
+	}
+
+      /* Disable exact length checking but adjust the minimum and maximum.  */
+      res->number_chars = -1;
+      if (res->number_chars_max != -1 && fmtres.max != -1)
+	res->number_chars_max += fmtres.max;
+
+      res->number_chars_min += fmtres.min;
+    }
+  else
+    {
+      if (!overflowed && 0 < fmtres.min && navail < (unsigned)fmtres.min)
+	{
+	  const char* fmtstr
+	    = (info.bounded
+	       ? (1 < fmtres.min
+		  ? G_("%<%.*s%> directive output truncated while writing "
+		       "%i bytes into a region of size %wu")
+		  : G_("%<%.*s%> directive output truncated while writing "
+		       "%i byte into a region of size %wu"))
+	       : (1 < fmtres.min
+		  ? G_("%<%.*s%> directive writing %i bytes "
+		       "into a region of size %wu")
+		  : G_("%<%.*s%> directive writing %i byte "
+		       "into a region of size %wu")));
+
+	  res->warned = warning_at (dirloc, OPT_Wformat_length_, fmtstr,
+				    (int)cvtlen, cvtbeg, fmtres.min, navail);
+	}
+      res->inc_number_chars (fmtres.min);
+    }
+}
+
+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 < 0)
+    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 number 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.  */
+  unsigned HOST_WIDE_INT navail = bytes_remaining (info.objsize, *res);
+
+  /* If the number of available bytes has wrapped around zero
+     the destination has already overflowed and been diagnosed so
+     avoid diagnosing it again.  In the diagnostic, distinguish between
+     a possible overflow ("may write") and a certain overflow somewhere
+     "past the end."  (Ditto for truncation.)  */
+  bool overflowed = HOST_WIDE_INT_MAX <= navail;
+  if (!overflowed && navail < nbytes)
+    {
+      /* 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 range from the last processed format character
+	 to the current one (or the end of the format string).  */
+      location_t loc = location_from_offset (info.fmtloc, off + 1, 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
+	   && (unsigned)res->number_chars_min < info.objsize);
+
+      res->warned = true;
+
+      if (!end && (nbytes - navail) == 1)
+	{
+	  /* There is room the rest of the format string but none
+	     for the terminating nul. */
+	  const char *text
+	    = (info.bounded   // Snprintf an 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")));
+
+	  warning_at (loc, 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 < 0
+		  ? 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")));
+
+	  warning_at (loc, OPT_Wformat_length_, text, info.fmtstr[off], off);
+	}
+    }
+
+  /* if (warn_format_length */
+  /*     && (overflowed || navail < nbytes */
+  /* 	  || (1 < warn_format_length && )) */
+  if (res->warned)
+    {
+      /* Help the user figure out how big a buffer they need.  */
+
+      location_t callloc = gimple_location (info.callstmt);
+
+      unsigned HOST_WIDE_INT min
+	= -1 < res->number_chars_min ? res->number_chars_min : -1;
+
+      unsigned HOST_WIDE_INT max
+	= -1 < res->number_chars_max ? res->number_chars_max : -1;
+
+      unsigned HOST_WIDE_INT exact
+	= -1 < res->number_chars ? res->number_chars : res->number_chars_min;
+
+      if (min < max && max != HOST_WIDE_INT_M1U)
+	inform (callloc,
+		"format output between %wu and %wu bytes into "
+		"a destination of size %wu",
+		min + nbytes, max + nbytes, info.objsize);
+      else
+	inform (callloc,
+		(nbytes + exact == 1
+		 ? "format output %wu byte into a destination of size %wu"
+		 : "format output %wu bytes into a destination of size %wu"),
+		nbytes + exact, info.objsize);
+    }
+
+  res->inc_number_chars (nbytes);
+}
+
+void
+pass_sprintf_length::compute_format_length (const call_info &info)
+{
+  /* Bail early if format length checking is disabled, either
+     via a command line option, or as a result of the size of
+     the destination object not being available, or due to
+     format directives or arguments encountered during processing
+     that prevent length tracking with any reliability.  */
+
+  if (HOST_WIDE_INT_MAX <= info.objsize)
+      return;
+
+  /* The variadic argument counter.  */
+  unsigned argno = info.argidx;
+
+  /* The aggregate result for the call.  */
+  format_result res = format_result ();
+
+  /* No directive has been seen yet so the output is bounded and constant
+     until determined otherwise.  */
+  res.bounded = true;
+  res.constant = true;
+
+  const char *pf = info.fmtstr;
+
+  for ( ; ; )
+    {
+      /* The beginning of the next format directive.  */
+      const char *dir = strchr (pf, '%');
+
+      /* Add the number of bytes between the end of the last directive
+	 and either the next if one exists, or the end of the format
+	 string.  */
+      add_bytes (info, pf, dir, &res);
+
+      if (!dir)
+	break;
+
+      pf = dir + 1;
+
+      if (*pf == 0)
+	{
+	  /* Incomplete directive.  */
+	  return;
+	}
+
+      conversion_spec spec = conversion_spec ();
+
+      /* POSIX numbered argument index or zero when none.  */
+      unsigned dollar = 0;
+
+      if (ISDIGIT (*pf))
+	{
+	  /* This could be either a POSIX positional argument, the '0'
+	     flag, or a width, depending on what follows.  Store it as
+	     width and sort it out later after the next character has
+	     been seen.  */
+	  char *end;
+	  spec.width = strtol (pf, &end, 10);
+	  spec.have_width = true;
+	  pf = end;
+	}
+      else if ('*' == *pf)
+	{
+	  /* Similarly to the block above, this could be either a POSIX
+	     positional argument or a width, depending on what follows.  */
+	  spec.star_width = gimple_call_arg (info.callstmt, argno++);
+	  ++pf;
+	}
+
+      if (*pf == '$')
+	{
+	  /* Handle the POSIX dollar sign which references the 1-based
+	     positional argument number.  */
+	  if (spec.have_width)
+	    dollar = spec.width + info.argidx;
+	  else if (spec.star_width
+		   && TREE_CODE (spec.star_width) == INTEGER_CST)
+	    dollar = spec.width + tree_to_shwi (spec.star_width);
+
+	  /* Bail when the numbered argument is out of range (it will
+	     have already been diagnosed by -Wformat).  */
+	  if (dollar == 0
+	      || dollar == info.argidx
+	      || dollar > gimple_call_num_args (info.callstmt))
+	    return;
+
+	  --dollar;
+
+	  spec.star_width = NULL_TREE;
+	  spec.have_width = false;
+	  ++pf;
+	}
+
+      if (dollar || !spec.star_width)
+	{
+	  if (spec.have_width && spec.width == 0)
+	    {
+	      /* The '0' that has been interpreted as a width above is
+		 actually a flag.  Reset HAVE_WIDTH, set the '0' flag,
+		 and continue processing other flags.  */
+	      spec.have_width = false;
+	      spec.set_flag ('0');
+	    }
+	  /* When either '$' has been seen, or width has not been seen,
+	     the next field is the optional flags followed by an optional
+	     width.  */
+	  for ( ; ; ) {
+	    switch (*pf)
+	      {
+	      case ' ':
+	      case '0':
+	      case '+':
+	      case '-':
+	      case '#':
+		spec.set_flag (*pf++);
+		break;
+
+	      default:
+		goto start_width;
+	      }
+	  }
+
+	start_width:
+	  if (ISDIGIT (*pf))
+	    {
+	      char *end;
+	      spec.width = strtol (pf, &end, 10);
+	      spec.have_width = true;
+	      pf = end;
+	    }
+	  else if ('*' == *pf)
+	    {
+	      spec.star_width = gimple_call_arg (info.callstmt, argno++);
+	      ++pf;
+	    }
+	  else if ('\'' == *pf)
+	    {
+	      /* The POSIX apostrophe indicating a numeric grouping
+		 in the current locale.  Even though it's possible to
+		 estimate the upper bound on the size of the output
+		 based on the number of digits it probably isn't worth
+		 continuing.  */
+	      return;
+	    }
+	}
+
+      if ('.' == *pf)
+	{
+	  ++pf;
+
+	  if (ISDIGIT (*pf))
+	    {
+	      char *end;
+	      spec.precision = strtol (pf, &end, 10);
+	      spec.have_precision = true;
+	      pf = end;
+	    }
+	  else if ('*' == *pf)
+	    {
+	      spec.star_precision = gimple_call_arg (info.callstmt, argno++);
+	      ++pf;
+	    }
+	  else
+	    return;
+	}
+
+      switch (*pf)
+	{
+	case 'h':
+	  if (pf[1] == 'h')
+	    {
+	      ++pf;
+	      spec.modifier = FMT_LEN_hh;
+	    }
+	  else
+	    spec.modifier = FMT_LEN_h;
+	  ++pf;
+	  break;
+
+	case 'j':
+	  spec.modifier = FMT_LEN_j;
+	  ++pf;
+	  break;
+
+	case 'L':
+	  spec.modifier = FMT_LEN_L;
+	  ++pf;
+	  break;
+
+	case 'l':
+	  if (pf[1] == 'l')
+	    {
+	      ++pf;
+	      spec.modifier = FMT_LEN_ll;
+	    }
+	  else
+	    spec.modifier = FMT_LEN_l;
+	  ++pf;
+	  break;
+
+	case 't':
+	  spec.modifier = FMT_LEN_t;
+	  ++pf;
+	  break;
+
+	case 'z':
+	  spec.modifier = FMT_LEN_z;
+	  ++pf;
+	  break;
+	}
+
+      switch (*pf)
+	{
+	case '%':
+	  spec.fmtfunc = format_percent;
+	  break;
+
+	case 'a':
+	case 'A':
+	case 'e':
+	case 'E':
+	case 'f':
+	case 'F':
+	case 'g':
+	case 'G':
+	  spec.fmtfunc = format_floating;
+	  break;
+
+	case 'd':
+	case 'i':
+	case 'o':
+	case 'p':
+	case 'u':
+	case 'x':
+	case 'X':
+	  spec.fmtfunc = format_integer;
+	  break;
+
+	case 'n':
+	  return;
+
+	case 'c':
+	case 'S':
+	case 's':
+	  spec.fmtfunc = format_string;
+	  break;
+
+	default:
+	  return;
+	}
+
+      spec.specifier = *pf++;
+
+      /* Compute the length of the format directive.  */
+      size_t dirlen = pf - dir;
+
+      /* Offset of the beginning of the directive from the beginning
+	 of the format string.  */
+      size_t diroff = dir - info.fmtstr;
+
+      /* Extract the argument (if the directive takes one).  */
+      tree arg
+	= (spec.specifier == '%'
+	   ? NULL_TREE : gimple_call_arg (info.callstmt,
+					  dollar ? dollar : argno++));
+
+      ::compute_format_length (info, &res, dir, dirlen, diroff, spec, arg);
+    }
+}
+
+/* Return the size of the object referenced by the expression DEST.
+   Try to work around some of the limitations of __builtin_object_size
+   in the common simple case when DEST is a POINTER_PLUS_EXPR involving
+   an array.  */
+
+static unsigned HOST_WIDE_INT
+get_destination_size (tree dest)
+{
+  /* Try to use __builtin_object_size although it rarely returns
+     a useful result even for straighforward cases.  */
+  tree ost = warn_format_length < 2
+    ? integer_zero_node : build_int_cst (size_type_node, 2);
+  tree args[] = { dest, ost };
+  tree func = builtin_decl_explicit (BUILT_IN_OBJECT_SIZE);
+  if (tree size = fold_builtin_n (UNKNOWN_LOCATION, func, args, 2, false))
+    return tree_to_uhwi (STRIP_NOPS  (size));
+
+  /* If __builtin_object_size fails to deliver, try to compute
+     it for the very basic (but common) cases.  */
+  if (TREE_CODE (dest) == SSA_NAME
+      && POINTER_TYPE_P (TREE_TYPE (dest)))
+    {
+      gimple *def = SSA_NAME_DEF_STMT (dest);
+      if (gimple_code (def) == GIMPLE_ASSIGN)
+	{
+	  tree_code code = gimple_assign_rhs_code (def);
+	  if (code == POINTER_PLUS_EXPR)
+	    {
+	      tree off = gimple_assign_rhs2 (def);
+	      dest = gimple_assign_rhs1 (def);
+
+	      if (cst_and_fits_in_hwi (off))
+		{
+		  unsigned HOST_WIDE_INT size = get_destination_size (dest);
+		  if (size != HOST_WIDE_INT_M1U)
+		    return size - tree_to_shwi (off);
+		}
+	    }
+	}
+    }
+
+  return -1;
+}
+
+/* Determine if a GIMPLE CALL is one to one of the sprintf-like built-in
+   functions and if so, handle it.  */
+
+void
+pass_sprintf_length::handle_gimple_call (gimple *call)
+{
+  call_info info = call_info ();
+
+  info.callstmt = call;
+  info.func = gimple_call_fn (info.callstmt);
+  if (!info.func)
+    return;
+
+  if (TREE_CODE (info.func) == ADDR_EXPR)
+    info.func = TREE_OPERAND (info.func, 0);
+
+  if (TREE_CODE (info.func) != FUNCTION_DECL)
+    return;
+
+  info.fncode = DECL_FUNCTION_CODE (info.func);
+
+  /* The size of the destination as in snprintf(dest, size, ...).  */
+  unsigned HOST_WIDE_INT dstsize = ~(unsigned HOST_WIDE_INT)0;
+
+  /* The size of the destination determined by __builtin_object_size.  */
+  unsigned HOST_WIDE_INT objsize = ~(unsigned HOST_WIDE_INT)0;
+
+  /* Buffer size argument number (snprintf and vsnprintf).  */
+  unsigned idx_dstsize = -1;
+
+  /* Object size argument number (snprintf_chk and vsnprintf_chk).  */
+  unsigned idx_objsize = -1;
+
+  /* Format string argument number (valid for all functions).  */
+  unsigned idx_format;
+
+  switch (info.fncode)
+    {
+    case BUILT_IN_SPRINTF:
+      // Signature:
+      //   __builtin_sprintf (dst, format, ...)
+      idx_format = 1;
+      info.argidx = 2;
+      break;
+
+    case BUILT_IN_SNPRINTF:
+      // Signature:
+      //   __builtin_snprintf (dst, size, format, ...)
+      idx_dstsize = 1;
+      idx_format = 2;
+      info.argidx = 3;
+      info.bounded = true;
+      break;
+
+    case BUILT_IN_SNPRINTF_CHK:
+      // Signature:
+      //   __builtin___sprintf_chk (dst, size, ost, objsize, format, ...)
+      idx_dstsize = 1;
+      idx_objsize = 3;
+      idx_format = 4;
+      info.argidx = 5;
+      info.bounded = true;
+      break;
+
+    case BUILT_IN_SPRINTF_CHK:
+      // Signature:
+      //   __builtin___sprintf_chk (dst, ost, objsize, format, ...)
+      idx_objsize = 2;
+      idx_format = 3;
+      info.argidx = 4;
+      break;
+
+    case BUILT_IN_VSNPRINTF:
+      // Signature:
+      //   __builtin_vsprintf (dst, size, format, va)
+      idx_dstsize = 1;
+      idx_format = 2;
+      info.argidx = -1;
+      info.bounded = true;
+      break;
+
+    case BUILT_IN_VSNPRINTF_CHK:
+      // Signature:
+      //   __builtin___vsnprintf_chk (dst, size, ost, objsize, format, va)
+      idx_dstsize = 1;
+      idx_objsize = 2;
+      idx_format = 3;
+      info.argidx = -1;
+      info.bounded = true;
+      break;
+
+    case BUILT_IN_VSPRINTF:
+      // Signature:
+      //   __builtin_vsprintf (dst, format, va)
+      idx_format = 1;
+      info.argidx = -1;
+      break;
+
+    case BUILT_IN_VSPRINTF_CHK:
+      // Signature:
+      //   __builtin___vsprintf_chk (dst, ost, objsize, format, va)
+      idx_format = 3;
+      idx_objsize = 2;
+      info.argidx = -1;
+      break;
+
+    default:
+      return;
+    }
+
+  info.format = gimple_call_arg (info.callstmt, idx_format);
+
+  if (idx_dstsize == -1U)
+    {
+      // For non-bounded functions like sprintf, to to determine
+      // the size of the destination from the object or pointer
+      // passed to it as the first argument.
+      dstsize = get_destination_size (gimple_call_arg (info.callstmt, 0));
+    }
+  else if (tree size = gimple_call_arg (info.callstmt, idx_dstsize))
+    {
+      /* For bounded functions try to get the size argument.  */
+
+      if (TREE_CODE (size) == INTEGER_CST)
+	{
+	  dstsize = tree_to_uhwi (size);
+	  if (dstsize > HOST_WIDE_INT_MAX)
+	    warning_at (gimple_location (info.callstmt), 0,
+			"specified destination size %wu too large",
+			dstsize);
+	}
+      else if (TREE_CODE (size) == SSA_NAME)
+	{
+	  /* Try to determine the range of values of the argument
+	     and use the greater of the two at -Wformat-level 1 and
+	     the smaller of them at level 2.  */
+	  wide_int min, max;
+	  enum value_range_type range_type
+	    = get_range_info (size, &min, &max);
+	  if (range_type == VR_RANGE)
+	    {
+	      dstsize
+		= (warn_format_length < 2
+		   ? wi::fits_uhwi_p (max) ? max.to_uhwi () : max.to_shwi ()
+		   : wi::fits_uhwi_p (min) ? min.to_uhwi () : min.to_shwi ());
+	    }
+	}
+    }
+
+  if (idx_objsize != -1U)
+    {
+      if (tree size = gimple_call_arg (info.callstmt, idx_objsize))
+	  if (tree_fits_uhwi_p (size))
+	    objsize = tree_to_uhwi (size);
+    }
+
+  if (info.bounded && !dstsize)
+    {
+      /* As a special case, bounded function allow the explicitly
+	 specified destination size argument to be zero as a request
+	 to determine the number of bytes on output without actually
+	 writing any output.  Disable length checking for those.  */
+      info.objsize = HOST_WIDE_INT_M1U;
+    }
+  else
+    {
+      /* Set the object size to the smaller of the two arguments
+	 of both have been specified and they're not equal.  */
+      info.objsize = dstsize < objsize ? dstsize : objsize;
+
+      if (info.bounded
+	  && dstsize != HOST_WIDE_INT_M1U && objsize < dstsize)
+	{
+	  warning_at (gimple_location (info.callstmt), 0,
+		      "specified size %wu exceeds the size %wu "
+		      "of the destination object", dstsize, objsize);
+	}
+    }
+
+  if (!info.format)
+    {
+      // Should this give a warning?
+      return;
+    }
+
+  info.fmtstr = get_format_string (info.format, &info.fmtloc);
+  if (!info.fmtstr)
+    return;
+
+  compute_format_length (info);
+}
+
+unsigned int
+pass_sprintf_length::execute (function *fun)
+{
+  basic_block bb;
+  FOR_EACH_BB_FN (bb, fun)
+    {
+      for (gimple_stmt_iterator si = gsi_start_bb (bb); !gsi_end_p (si);
+	   gsi_next (&si))
+	{
+	  /* Iterate over statements, looking for function calls.  */
+	  gimple *stmt = gsi_stmt (si);
+
+	  if (gimple_code (stmt) == GIMPLE_CALL)
+	    handle_gimple_call (stmt);
+	}
+    }
+
+  return 0;
+}
+
+gimple_opt_pass *
+make_pass_sprintf_length (gcc::context *ctxt)
+{
+  return new pass_sprintf_length (ctxt);
+}
diff --git a/gcc/gimplify.c b/gcc/gimplify.c
index fb27dd0..da83e52 100644
--- a/gcc/gimplify.c
+++ b/gcc/gimplify.c
@@ -5346,7 +5346,7 @@  gimplify_asm_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
 	     flexibility, split it into separate input and output
  	     operands.  */
 	  tree input;
-	  char buf[10];
+	  char buf[11];   /* Big enough for a 32-bit UINT_MAX.  */
 
 	  /* Turn the in/out constraint into an output constraint.  */
 	  char *p = xstrdup (constraint);
@@ -5356,7 +5356,7 @@  gimplify_asm_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
 	  /* And add a matching input constraint.  */
 	  if (allows_reg)
 	    {
-	      sprintf (buf, "%d", i);
+	      sprintf (buf, "%u", i);
 
 	      /* If there are multiple alternatives in the constraint,
 		 handle each of them individually.  Those that allow register
 /* Set the DECL_ASSEMBLER_NAME for DECL.  */
 void
 lhd_set_decl_assembler_name (tree decl)
diff --git a/gcc/passes.c b/gcc/passes.c
index 0565cfa..c2d0c1f 100644
--- a/gcc/passes.c
+++ b/gcc/passes.c
@@ -771,7 +771,7 @@  pass_manager::register_one_dump_file (opt_pass *pass)
 {
   char *dot_name, *flag_name, *glob_name;
   const char *name, *full_name, *prefix;
-  char num[10];
+  char num[11];   /* Big enough for a 32-bit UINT_MAX.  */
   int flags, id;
   int optgroup_flags = OPTGROUP_NONE;
   gcc::dump_manager *dumps = m_ctxt->get_dumps ();
@@ -779,7 +779,7 @@  pass_manager::register_one_dump_file (opt_pass *pass)
   /* See below in next_pass_1.  */
   num[0] = '\0';
   if (pass->static_pass_number != -1)
-    sprintf (num, "%d", ((int) pass->static_pass_number < 0
+    sprintf (num, "%u", ((int) pass->static_pass_number < 0
 			 ? 1 : pass->static_pass_number));
 
   /* The name is both used to identify the pass for the purposes of plugins,
@@ -2425,8 +2425,15 @@  execute_pass_list_1 (opt_pass *pass)
 
       if (cfun == NULL)
 	return;
+
+      // inform (0, "executing pass: %s", pass->name);
+
       if (execute_one_pass (pass) && pass->sub)
-        execute_pass_list_1 (pass->sub);
+	{
+	  // inform (0, "executing subpass: %s", pass->sub->name);
+	  execute_pass_list_1 (pass->sub);
+	}
+
       pass = pass->next;
     }
   while (pass);
diff --git a/gcc/passes.def b/gcc/passes.def
index 3647e90..ac954cd 100644
--- a/gcc/passes.def
+++ b/gcc/passes.def
@@ -43,6 +43,7 @@  along with GCC; see the file COPYING3.  If not see
   NEXT_PASS (pass_warn_function_return);
   NEXT_PASS (pass_expand_omp);
   NEXT_PASS (pass_build_cgraph_edges);
+  NEXT_PASS (pass_sprintf_length, false);
   TERMINATE_PASS_LIST (all_lowering_passes)
 
   /* Interprocedural optimization passes.  */
@@ -303,6 +304,7 @@  along with GCC; see the file COPYING3.  If not see
       NEXT_PASS (pass_simduid_cleanup);
       NEXT_PASS (pass_lower_vector_ssa);
       NEXT_PASS (pass_cse_reciprocals);
+      NEXT_PASS (pass_sprintf_length, true);
       NEXT_PASS (pass_reassoc, false /* insert_powi_p */);
       NEXT_PASS (pass_strength_reduction);
       NEXT_PASS (pass_split_paths);
diff --git a/gcc/print-tree.c b/gcc/print-tree.c
index 468f1ff..a3e6313 100644
--- a/gcc/print-tree.c
+++ b/gcc/print-tree.c
@@ -694,8 +694,8 @@  print_node (FILE *file, const char *prefix, tree node, int indent)
 	  i = 0;
 	  FOR_EACH_CALL_EXPR_ARG (arg, iter, node)
 	    {
-	      char temp[10];
-	      sprintf (temp, "arg %d", i);
+	      char temp[15];   /* Big enough for a 32-bit UINT_MAX.  */
+	      sprintf (temp, "arg %u", i);
 	      print_node (file, temp, arg, indent + 4);
 	      i++;
 	    }
@@ -706,7 +706,7 @@  print_node (FILE *file, const char *prefix, tree node, int indent)
 
 	  for (i = 0; i < len; i++)
 	    {
-	      char temp[10];
+	      char temp[15];   /* Big enough for a 32-bit UINT_MAX.  */
 
 	      sprintf (temp, "arg %d", i);
 	      print_node (file, temp, TREE_OPERAND (node, i), indent + 4);
@@ -814,7 +814,7 @@  print_node (FILE *file, const char *prefix, tree node, int indent)
 	  for (i = 0; i < len; i++)
 	    if (TREE_VEC_ELT (node, i))
 	      {
-		char temp[10];
+		char temp[15];   /* Big enough for a 32-bit UINT_MAX.  */
 		sprintf (temp, "elt %d", i);
 		print_node (file, temp, TREE_VEC_ELT (node, i), indent + 4);
 	      }
diff --git a/gcc/testsuite/gcc.dg/format/c99-sprintf-length-1.c b/gcc/testsuite/gcc.dg/format/c99-sprintf-length-1.c
new file mode 100644
index 0000000..d887ef2
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/format/c99-sprintf-length-1.c
@@ -0,0 +1,1295 @@ 
+/* { dg-do compile } */
+/* { dg-options "-std=c99 -Wformat -Wformat-length=1 -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
+   below make use of LINE to avoid warnings for other lines.  */
+#ifndef LINE
+# define LINE 0
+#endif
+
+char buffer [256];
+extern char *ptr;
+
+#define buffer(size)							\
+  (!LINE || __LINE__ == LINE ? buffer + sizeof buffer - size : ptr)
+
+#define objsize(size)  (!LINE || __LINE__ == LINE ? size : __SIZE_MAX__ / 2)
+
+typedef __SIZE_TYPE__ size_t;
+
+#if !__cplusplus
+typedef __WCHAR_TYPE__ wchar_t;
+#endif
+
+typedef unsigned char UChar;
+
+const char s0[] = "";
+const char s1[] = "1";
+const char s2[] = "12";
+const char s3[] = "123";
+const char s4[] = "1234";
+const char s5[] = "12345";
+const char s6[] = "123456";
+const char s7[] = "1234567";
+const char s8[] = "12345678";
+
+void sink (void*);
+
+/* Macro to verify that calls to __builtin_sprintf (i.e., with no size
+   argument) issue diagnostics by correctly determining the size of
+   the destination buffer.  */
+#define T(size, fmt, ...)						\
+  __builtin_sprintf (buffer (size), fmt, __VA_ARGS__), sink (buffer);
+
+/* Exercise the "%c" and "%lc" directive with constant arguments.  */
+
+void test_sprintf_c_const (void)
+{
+  T (0, "%c",     0);           /* { dg-warning ".%c. directive writing 1 byte into a region of size 0" } */
+  T (1, "%c",     0);           /* { dg-warning "writing a terminating nul past the end" } */
+  T (1, "%c",   '1');           /* { dg-warning "nul past the end" } */
+  T (2, "%c",   '1');
+  T (2, "%2c",  '1');           /* { dg-warning "nul past the end" } */
+  T (2, "%3c",  '1');           /* { dg-warning "into a region" } */
+  T (2, "%c%c", '1', '2');      /* { dg-warning "nul past the end" } */
+  T (3, "%c%c", '1', '2');
+
+  T (2, "%1$c%2$c", '1', '2');  /* { dg-warning "does not support %n.|nul past the end" } */
+  T (3, "%1$c%2$c", '1', '2');
+}
+
+/* Exercise the "%p" directive with constant arguments.  */
+
+void test_sprintf_p_const (void)
+{
+  /* The exact output for %p is unspecified by C.  It's based on GLIBC
+     output */
+  T (0, "%p",     (void*)0x1);    /* { dg-warning ".%p. directive writing 3 bytes into a region of size 0" } */
+  T (1, "%p",     (void*)0x12);   /* { dg-warning ".%p. directive writing 4 bytes into a region of size 1" } */
+  T (2, "%p",     (void*)0x123);  /* { dg-warning ".%p. directive writing 5 bytes into a region of size 2" } */
+
+  /* GLIBC treats the ' ' flag with the "%p" directive the same as with
+     signed integer conversions (i.e., it prepends a space).  */
+  T (5, "% p",    (void*)0x234);  /* { dg-warning ". . flag used with .%p.|.% p. directive writing 6 bytes into a region of size 5" } */
+}
+
+/* Verify that no warning is issued for calls that write into a flexible
+   array member whose size isn't known.  Also verify that calls that use
+   a flexible array member as an argument to the "%s" directive do not
+   cause a warning.  */
+
+void test_sprintf_flexarray (void *p, int i)
+{
+  struct S
+  {
+    int n;
+    char a [];
+  } *s = p;
+
+  __builtin_sprintf (s->a, "%c",       'x');
+
+  __builtin_sprintf (s->a, "%s",       "");
+  __builtin_sprintf (s->a, "%s",       "abc");
+  __builtin_sprintf (s->a, "abc%sghi", "def");
+
+  __builtin_sprintf (s->a, "%i",       1234);
+
+  __builtin_sprintf (buffer (1), "%s",  s->a);
+  __builtin_sprintf (buffer (1), "%s",  s [i].a);
+}
+
+/* Verify that the note printed along with the diagnostic mentions
+   the correct sizes and refers to the location corresponding to
+   the affected directive.  */
+
+void test_sprintf_note (void)
+{
+#define P __builtin_sprintf
+
+  /* Diagnostic column numbers are 1-based.  */
+
+  P (buffer (0),                /* { dg-message "format output 4 bytes into a destination of size 0" } */
+     "%c%s%i", '1', "2", 3);    /* { dg-warning "7:.%c. directive writing 1 byte into a region of size 0" } */
+
+  P (buffer (1),                /* { dg-message "format output 6 bytes into a destination of size 1" } */
+     "%c%s%i", '1', "23", 45);  /* { dg-warning "9:.%s. directive writing 2 bytes into a region of size 0" } */
+
+  P (buffer (2),                /* { dg-message "format output 6 bytes into a destination of size 2" } */
+     "%c%s%i", '1', "2", 345);  /* { dg-warning "11:.%i. directive writing 3 bytes into a region of size 0" } */
+
+  P (buffer (6),                /* { dg-message "format output 7 bytes into a destination of size 6" } */
+     "%c%s%i", '1', "2", 3456); /* { dg-warning "13:writing a terminating nul past the end of the destination" } */
+}
+
+#undef T
+#define T(size, fmt, ...)						\
+  __builtin___sprintf_chk (buffer (size), 0, objsize (size), fmt, __VA_ARGS__)
+
+/* Exercise the "%c" and "%lc" directive with constant arguments.  */
+
+void test_sprintf_chk_c_const (void)
+{
+  /* Verify the full text of the diagnostic for just the distinct messages
+     and use abbreviations in subsequent test cases.  */
+  T (0, "%c",     0);            /* { dg-warning ".%c. directive writing 1 byte into a region of size 0" } */
+  T (1, "%c",     0);            /* { dg-warning "writing a terminating nul past the end" } */
+  T (1, "%c",   '1');            /* { dg-warning "nul past the end" } */
+  T (2, "%c",   '1');
+  T (2, "%2c",  '1');            /* { dg-warning "nul past the end" } */
+  T (2, "%3c",  '1');            /* { dg-warning "into a region" } */
+  T (2, "%c%c", '1', '2');       /* { dg-warning "nul past the end" } */
+  T (3, "%c%c", '1', '2');
+
+  /* Wide characters.  */
+  T (0, "%lc",     0);           /* { dg-warning "nul past the end" } */
+  T (1, "%lc",     0);
+  T (1, "%lc%lc",  0, 0);
+  T (2, "%lc",     0);
+  T (2, "%lc%lc",  0, 0);
+
+  /* The following could result in as few as no bytes 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",  L'1');
+  T (2, "%1lc", L'1');
+  /* Writing some unknown number of bytes into a field two characters wide.  */
+  T (2, "%2lc", L'1');          /* { dg-warning "nul past the end" } */
+
+  T (3, "%lc%c",   L'1', '2');
+  /* 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", L'1', '2', '3');   /* { dg-warning "nul past the end" } */
+  T (3, "%lc%lc%c", L'1', L'2', '3'); /* { dg-warning "nul past the end" } */
+}
+
+/* Exercise the "%s" and "%ls" directive with constant arguments.  */
+
+void test_sprintf_chk_s_const (void)
+{
+  T (0, "%*s",  0, "");         /* { dg-warning "nul past the end" } */
+  T (0, "%*s",  0, s0);         /* { dg-warning "nul past the end" } */
+  T (1, "%*s",  0, "");
+  T (1, "%*s",  0, s0);
+  T (1, "%*s",  0, "\0");
+  T (1, "%*s",  0, "1");        /* { dg-warning "nul past the end" } */
+  T (1, "%*s",  0, s1);         /* { dg-warning "nul past the end" } */
+  T (1, "%1s",     "");         /* { dg-warning "nul past the end" } */
+  T (1, "%1s",     s0);         /* { dg-warning "nul past the end" } */
+  T (1, "%*s",  1, "");         /* { dg-warning "nul past the end" } */
+  T (1, "%*s",  1, s0);         /* { dg-warning "nul past the end" } */
+
+  T (1, "%.0s",    "123");
+  T (1, "%.0s",    s3);
+  T (1, "%.*s", 0, "123");
+  T (1, "%.*s", 0, s3);
+  T (1, "%.1s",    "123");      /* { dg-warning "nul past the end" } */
+  T (1, "%.1s",    s3);         /* { dg-warning "nul past the end" } */
+  T (1, "%.*s", 1, "123");      /* { dg-warning "nul past the end" } */
+  T (1, "%.*s", 1, s3);         /* { dg-warning "nul past the end" } */
+
+  T (2, "%.*s", 0, "");
+  T (2, "%.*s", 0, "1");
+  T (2, "%.*s", 0, s1);
+  T (2, "%.*s", 0, "1\0");
+  T (2, "%.*s", 0, "12");
+  T (2, "%.*s", 0, s2);
+
+  T (2, "%.*s", 1, "");
+  T (2, "%.*s", 1, "1");
+  T (2, "%.*s", 1, s1);
+  T (2, "%.*s", 1, "1\0");
+  T (2, "%.*s", 1, "12");
+  T (2, "%.*s", 1, s2);
+
+  T (2, "%.*s", 2, "");
+  T (2, "%.*s", 2, "1");
+  T (2, "%.*s", 2, s1);
+  T (2, "%.*s", 2, "1\0");
+  T (2, "%.*s", 2, "12");       /* { dg-warning "nul past the end" } */
+  T (2, "%.*s", 2, s2);         /* { dg-warning "nul past the end" } */
+
+  T (2, "%.*s", 3, "");
+  T (2, "%.*s", 3, "1");
+  T (2, "%.*s", 3, s1);
+  T (2, "%.*s", 3, "1\0");
+  T (2, "%.*s", 3, "12");       /* { dg-warning "nul past the end" } */
+  T (2, "%.*s", 3, "123");      /* { dg-warning "into a region" } */
+  T (2, "%.*s", 3, s2);         /* { dg-warning "nul past the end" } */
+  T (2, "%.*s", 3, s3);         /* { dg-warning "into a region" } */
+
+  T (2, "%*s",  0, "");
+  T (2, "%*s",  0, "1");
+  T (2, "%*s",  0, s1);
+  T (2, "%*s",  0, "1\0");
+  T (2, "%*s",  0, "12");       /* { dg-warning "nul past the end" } */
+  T (2, "%*s",  0, s2);         /* { dg-warning "nul past the end" } */
+
+  /* Multiple directives.  */
+
+  T (1, "%s%s", "", "");
+  T (1, "%s%s", s0, s0);
+  T (1, "%s%s", "", "1");       /* { dg-warning "nul past the end" } */
+  T (1, "%s%s", s0, s1);        /* { dg-warning "nul past the end" } */
+  T (1, "%s%s", "1", "");       /* { dg-warning "nul past the end" } */
+  T (1, "%s%s", s1, s0);        /* { dg-warning "nul past the end" } */
+  T (1, "%s%s", "1", "2");      /* { dg-warning "into a region" } */
+  T (1, "%s%s", s1, s1);        /* { dg-warning "into a region" } */
+
+  T (2, "%s%s", "", "");
+  T (2, "%s%s", "", "1");
+  T (2, "%s%s", "1", "");
+  T (2, "%s%s", "", "12");      /* { dg-warning "nul past the end" } */
+  T (2, "%s%s", "1", "2");      /* { dg-warning "nul past the end" } */
+  T (2, "%s%s", "12", "2");     /* { dg-warning "into a region" } */
+  T (2, "%s%s", "1", "23");     /* { dg-warning "into a region" } */
+  T (2, "%s%s", "12", "3");     /* { dg-warning "into a region" } */
+  T (2, "%s%s", "12", "34");    /* { dg-warning "into a region" } */
+
+  T (2, "_%s",   "");
+  T (2, "%%%s",  "");
+  T (2, "%s%%",  "");
+  T (2, "_%s",   "1");          /* { dg-warning "nul past the end" } */
+  T (2, "%%%s",  "1");          /* { dg-warning "nul past the end" } */
+  T (2, "%s%%",  "1");          /* { dg-warning "nul past the end" } */
+  T (2, "_%s",   "12");         /* { dg-warning "into a region" } */
+  T (2, "__%s",  "1");          /* { dg-warning "into a region" } */
+
+  T (2, "%1$s%2$s", "12", "3"); /* { dg-warning ".%2.s. directive writing 1 byte into a region of size 0" } */
+  T (2, "%1$s%1$s", "12");      /* { dg-warning "does not support|.%1.s. directive writing 2 bytes into a region of size 0" } */
+  T (2, "%2$s%1$s", "1", "23"); /* { dg-warning ".%1.s. directive writing 1 byte into a region of size 0" } */
+  T (2, "%2$s%2$s", "1", "23"); /* { dg-warning "unused|%2.s. directive writing 2 bytes into a region of size 0" } */
+
+  T (3, "__%s", "");
+  T (3, "__%s", "1");           /* { dg-warning "nul past the end" } */
+  T (3, "%s_%s", "", "");
+  T (3, "%s_%s", "1", "");
+  T (3, "%s_%s", "", "1");
+  T (3, "%s_%s", "1", "2");     /* { dg-warning "nul past the end" } */
+
+  /* Wide strings.  */
+  T (0, "%ls",      L"");       /* { dg-warning "nul past the end" } */
+  T (1, "%ls",      L"");
+  T (1, "%ls",      L"\0");
+  T (1, "%1ls",     L"");       /* { dg-warning "nul past the end" } */
+
+  T (0, "%*ls",  0, L"");       /* { dg-warning "nul past the end" } */
+  T (1, "%*ls",  0, L"");
+  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" } */
+  T (1, "%.0ls",    L"1");
+  T (2, "%.0ls",    L"1");
+  T (2, "%.1ls",    L"1");
+  T (2, "%.*ls", 1, L"1");
+
+  /* The "%.2ls" directive below will write at a minimum 1 byte (because
+     L"1" is known and can be assumed to convert to at least one multibyte
+     character), and at most 2 bytes because of the precision.  Since its
+     output is explicitly bounded it is diagnosed.  */
+  T (2, "%.2ls",    L"1");      /* { dg-warning "nul past the end" } */
+  T (2, "%.*ls", 2, L"1");      /* { dg-warning "nul past the end" } */
+
+  T (3, "%.0ls",    L"1");
+  T (3, "%.1ls",    L"1");
+  T (3, "%.2ls",    L"1");
+}
+
+/* Exercise the "%hhd", "%hhi", "%hho", "%hhu", and "%hhx" directives
+   with constant arguments.  */
+
+void test_sprintf_chk_hh_const (void)
+{
+  T (1, "%hhd",         0);     /* { dg-warning "nul past the end" } */
+  T (1, "%hhd",         1);     /* { dg-warning "nul past the end" } */
+  T (1, "%hhd",        -1);     /* { dg-warning "into a region" } */
+  T (1, "%+hhd",        0);     /* { dg-warning "into a region" } */
+  T (1, "%+hhd",        1);     /* { dg-warning "into a region" } */
+  T (1, "%-hhd",        0);     /* { dg-warning "nul past the end" } */
+
+  T (1, "%hhi",         0);     /* { dg-warning "nul past the end" } */
+  T (1, "%hhi",         1);     /* { dg-warning "nul past the end" } */
+  T (1, "%hhi",        -1);     /* { dg-warning "into a region" } */
+  T (1, "%+hhi",        0);     /* { dg-warning "into a region" } */
+  T (1, "%+hhi",        1);     /* { dg-warning "into a region" } */
+  T (1, "%-hhi",        0);     /* { dg-warning "nul past the end" } */
+
+  T (2, "%hhi",         0);
+  T (2, "%hhi",         1);
+  T (2, "%hhi",         9);
+  T (2, "% hhi",        9);     /* { dg-warning "nul past the end" } */
+  T (2, "%+hhi",        9);     /* { dg-warning "nul past the end" } */
+  T (2, "%-hhi",        9);
+  T (2, "%hhi",        10);     /* { dg-warning "nul past the end" } */
+  T (2, "%hhi",        -1);     /* { dg-warning "nul past the end" } */
+  T (2, "% hhi",       -1);     /* { dg-warning "nul past the end" } */
+  T (2, "%+hhi",       -1);     /* { dg-warning "nul past the end" } */
+  T (2, "%-hhi",       -1);     /* { dg-warning "nul past the end" } */
+
+  T (2, "%hho",         0);
+  T (2, "%hho",         1);
+  T (2, "%hho",         7);
+  T (2, "%hho",       010);     /* { dg-warning "nul past the end" } */
+  T (2, "%hho",       077);     /* { dg-warning "nul past the end" } */
+  T (2, "%hho",        -1);     /* { dg-warning "into a region" } */
+
+  T (2, "%hhx",         0);
+  T (2, "%hhX",         1);
+  T (2, "%hhx",         7);
+  T (2, "%hhX",         8);
+  T (2, "%hhx",        -1);     /* { dg-warning "nul past the end" } */
+  T (2, "%hhX",       0xf);
+  T (2, "%hhx",      0x10);     /* { dg-warning "nul past the end" } */
+  T (2, "%hhX",      0xff);     /* { dg-warning "nul past the end" } */
+
+  T (1, "%#hhx",        0);     /* { dg-warning "nul past the end" } */
+  T (2, "%#hhx",        0);
+  T (3, "%#hhx",        1);     /* { dg-warning "nul past the end" } */
+
+  T (4, "%hhd",       255);
+  T (4, "%hhd",       256);
+  T (4, "%hhd",     0xfff);
+  T (4, "%hhd",    0xffff);
+
+  T (4, "%hhi",       255);
+  T (4, "%hhi",       256);
+  T (4, "%hhi",     0xfff);
+  T (4, "%hhi",    0xffff);
+
+  T (4, "%hhu",        -1);
+  T (4, "%hhu",       255);
+  T (4, "%hhu",       256);
+  T (4, "%hhu",     0xfff);
+  T (4, "%hhu",    0xffff);
+
+  T (4, "%#hhx",        0);
+  T (4, "%#hhx",        1);
+  T (4, "%#hhx",       -1);     /* { dg-warning "nul past the end" } */
+  T (4, "%#hhx",      0xf);
+  T (4, "%#hhx",     0x10);     /* { dg-warning "nul past the end" } */
+  T (4, "%#hhx",     0xff);     /* { dg-warning "nul past the end" } */
+  T (4, "%#hhx",    0xfff);     /* { dg-warning "nul past the end" } */
+
+  T (4, "%hhi %hhi",  0,  0);
+  T (4, "%hhi %hhi",  9,  9);
+  T (4, "%hhi %hhi",  1, 10);   /* { dg-warning "nul past the end" } */
+  T (4, "%hhi %hhi", 10,  1);   /* { dg-warning "nul past the end" } */
+  T (4, "%hhi %hhi", 11, 12);   /* { dg-warning "into a region" } */
+
+  T (5, "%0*hhd %0*hhi", 0,  7, 0,   9);
+  T (5, "%0*hhd %0*hhi", 1,  7, 1,   9);
+  T (5, "%0*hhd %0*hhi", 1,  7, 2,   9);
+  T (5, "%0*hhd %0*hhi", 2,  7, 1,   9);
+  T (5, "%0*hhd %0*hhi", 2,  7, 2,   9); /* { dg-warning "nul past the end" } */
+  T (5, "%0*hhd %0*hhi", 0, 12, 0, 123); /* { dg-warning ".%0\\*hhi. directive writing 3 bytes into a region of size 2" } */
+  T (5, "%0*hhd %0*hhi", 1, 12, 1, 123); /* { dg-warning ".%0\\*hhi. directive writing 3 bytes into a region of size 2" } */
+  T (5, "%0*hhd %0*hhi", 2, 12, 3, 123); /* { dg-warning ".%0\\*hhi. directive writing 3 bytes into a region of size 2" } */
+
+  /* FIXME: Move the boundary test cases into a file of their own that's
+     exercised only on targets with the matching type limits (otherwise
+     they'll fail).  */
+#undef MAX
+#define MAX   127
+
+#undef MIN
+#define MIN   (-MAX -1)
+
+  T (1, "%hhi",        MAX);    /* { dg-warning "into a region" } */
+  T (1, "%hhi",        MIN);    /* { dg-warning "into a region" } */
+  T (1, "%hhi",  MAX +   1);    /* { dg-warning "into a region" } */
+
+  T (2, "%hhi",  MAX +   1);    /* { dg-warning "into a region" } */
+  T (2, "%hhi",  MAX +  10);    /* { dg-warning "into a region" } */
+  T (2, "%hhi",  MAX + 100);    /* { dg-warning "into a region" } */
+}
+
+/* Exercise the "%hhd", "%hi", "%ho", "%hu", and "%hx" directives
+   with constant arguments.  */
+
+void test_sprintf_chk_h_const (void)
+{
+  T (1, "%hu",          0);     /* { dg-warning "nul past the end" } */
+  T (1, "%hu",          1);     /* { dg-warning "nul past the end" } */
+  T (1, "%hu",         -1);     /* { dg-warning "into a region" } */
+
+  T (2, "%hi",          0);
+  T (2, "%hi",          1);
+  T (2, "%hi",          9);
+  T (2, "% hi",         9);     /* { dg-warning "nul past the end" } */
+  T (2, "%+hi",         9);     /* { dg-warning "nul past the end" } */
+  T (2, "%-hi",         9);
+  T (2, "%hi",         10);     /* { dg-warning "nul past the end" } */
+  T (2, "%hi",         -1);     /* { dg-warning "nul past the end" } */
+  T (2, "% hi",        -2);     /* { dg-warning "nul past the end" } */
+  T (2, "%+hi",        -3);     /* { dg-warning "nul past the end" } */
+  T (2, "%-hi",        -4);     /* { dg-warning "nul past the end" } */
+
+  T (2, "%hu",          0);
+  T (2, "%hu",          1);
+  T (2, "%hu",          9);
+  T (2, "%hu",         10);     /* { dg-warning "nul past the end" } */
+  T (2, "%hu",         -1);     /* { dg-warning "into a region" } */
+
+  T (2, "%ho",          0);
+  T (2, "%ho",          1);
+  T (2, "%ho",          7);
+  T (2, "%ho",        010);     /* { dg-warning "nul past the end" } */
+  T (2, "%ho",        077);     /* { dg-warning "nul past the end" } */
+  T (2, "%ho",       0100);     /* { dg-warning "into a region" } */
+  T (2, "%ho",         -1);     /* { dg-warning "into a region" } */
+
+  T (2, "%hx",          0);
+  T (2, "%hx",          1);
+  T (2, "%hx",          7);
+  T (2, "%hx",        0xf);
+  T (2, "%hx",       0x10);     /* { dg-warning "nul past the end" } */
+  T (2, "%hx",       0xff);     /* { dg-warning "nul past the end" } */
+  T (2, "%hx",      0x100);     /* { dg-warning "into a region" } */
+  T (2, "%hx",         -1);     /* { dg-warning "into a region" } */
+
+  T (3, "% hi",         7);
+  T (3, "%+hi",         8);
+  T (3, "%-hi",         9);
+  T (3, "%hi",         10);
+  T (3, "%hi",         -1);
+  T (3, "% hi",        -2);
+  T (3, "%+hi",        -3);
+  T (3, "%-hi",        -4);
+
+  T (5, "%hu",       9999);
+  T (5, "%hu",      10000);     /* { dg-warning "nul past the end" } */
+  T (5, "%hu",      65535);     /* { dg-warning "nul past the end" } */
+
+  T (1, "%#hx",         0);     /* { dg-warning "nul past the end" } */
+  T (2, "%#hx",         0);
+  T (3, "%#hx",         1);     /* { dg-warning "nul past the end" } */
+
+  T (4, "%#hx",         0);
+  T (4, "%#hx",         1);
+  T (4, "%#hx",       0xf);
+  T (4, "%#hx",      0x10);     /* { dg-warning "nul past the end" } */
+  T (4, "%#hx",      0xff);     /* { dg-warning "nul past the end" } */
+  T (4, "%#hx",     0x100);     /* { dg-warning "into a region" } */
+  T (4, "%#hx",        -1);     /* { dg-warning "into a region" } */
+
+#undef MAX
+#define MAX   65535
+
+  T (1, "%hhu",         0);     /* { dg-warning "nul past the end" } */
+  T (1, "%hhu",         1);     /* { dg-warning "nul past the end" } */
+  T (1, "%hhu",        -1);     /* { dg-warning "into a region" } */
+  T (1, "%hhu",       MAX);     /* { dg-warning "into a region" } */
+  T (1, "%hhu",  MAX +  1);     /* { dg-warning "nul past the end" } */
+}
+
+/* Exercise the "%d", "%i", "%o", "%u", and "%x" directives with
+   constant arguments.  */
+
+void test_sprintf_chk_integer_const (void)
+{
+  T ( 1, "%i",          0);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%i",          1);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%i",         -1);         /* { dg-warning "into a region" } */
+  T ( 1, "%i_",         1);         /* { dg-warning "character ._. at offset 2 past the end" } */
+  T ( 1, "_%i",         1);         /* { dg-warning "into a region" } */
+  T ( 1, "_%i_",        1);         /* { dg-warning "into a region" } */
+  T ( 1, "%o",          0);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%u",          0);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%x",          0);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%#x",         0);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%x",          1);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%#x",         1);         /* { dg-warning "into a region" } */
+
+  T ( 2, "%i",          0);
+  T ( 2, "%i",          1);
+  T ( 2, "%i",          9);
+  T ( 2, "%i",         -1);         /* { dg-warning "nul past the end" } */
+  T ( 2, "%i",         10);         /* { dg-warning "nul past the end" } */
+  T ( 2, "%i_",         0);         /* { dg-warning "nul past the end" } */
+  T ( 2, "_%i",         0);         /* { dg-warning "nul past the end" } */
+  T ( 2, "_%i_",        0);         /* { dg-warning "character ._. at offset 3 past the end" } */
+  T ( 2, "%o",          1);
+  T ( 2, "%o",          7);
+  T ( 2, "%o",        010);         /* { dg-warning "nul past the end" } */
+  T ( 2, "%o",       0100);         /* { dg-warning "into a region" } */
+  T ( 2, "%x",          1);
+  T ( 2, "%#x",         1);         /* { dg-warning "into a region" } */
+  T ( 2, "%x",        0xa);
+  T ( 2, "%x",        0xf);
+  T ( 2, "%x",       0x10);         /* { dg-warning "nul past the end" } */
+  T ( 2, "%x",       0xff);         /* { dg-warning "nul past the end" } */
+  T ( 2, "%x",      0x1ff);         /* { dg-warning "into a region" } */
+
+  T ( 3, "%i",          0);
+  T ( 3, "%i",          1);
+  T ( 3, "%i",          9);
+  T ( 3, "%i",         -9);
+  T ( 3, "%i",         10);
+  T ( 3, "%i",         99);
+  T ( 3, "%i",        -99);         /* { dg-warning "nul past the end" } */
+
+  /* ~0U is formatted into exactly three bytes as "-1" followed by
+     the terminating NUL character.  */
+  T ( 3, "%+i",       ~0U);
+  T ( 3, "%-i",       ~0U);
+  T ( 3, "% i",       ~0U);
+
+  T ( 8, "%8u",         1);        /* { dg-warning "nul past the end" } */
+  T ( 9, "%8u",         1);
+
+  T ( 7, "%1$i%2$i%3$i",     1, 23, 456);
+  T ( 8, "%1$i%2$i%3$i%1$i", 1, 23, 456);
+  T ( 8, "%1$i%2$i%3$i%2$i", 1, 23, 456);   /* { dg-warning "nul past the end" } */
+  T ( 8, "%1$i%2$i%3$i%3$i", 1, 23, 456);   /* { dg-warning "into a region" } */
+
+#undef MAX
+#define MAX   2147483647   /* 10 digits. */
+#undef MIN
+#define MIN   (-MAX -1)    /* Sign plus 10 digits. */
+
+  T ( 1, "%i",        MAX);         /* { dg-warning "into a region" } */
+  T ( 1, "%i",        MIN);         /* { dg-warning "into a region" } */
+  T ( 2, "%i",        MAX);         /* { dg-warning "into a region" } */
+  T ( 2, "%i",        MIN);         /* { dg-warning "into a region" } */
+  T (10, "%i",  123456789);
+  T (10, "%i", -123456789);         /* { dg-warning "nul past the end" } */
+  T (10, "%i",        MAX);         /* { dg-warning "nul past the end" } */
+  T (10, "%i",        MIN);         /* { dg-warning "into a region" } */
+
+  T (11, "%i",        MAX);
+  T (11, "%i",        MIN);         /* { dg-warning "nul past the end" } */
+}
+
+/* Exercise the "%jd", "%ji", "%jo", "%ju", and "%jx" directives
+   for the formatting of intmax_t and uintmax_t values with constant
+   arguments.  */
+
+void test_sprintf_chk_j_const (void)
+{
+#define I(x) ((__INTMAX_TYPE__)x)
+
+  T ( 1, "%ji",  I (    0));      /* { dg-warning "nul past the end" } */
+  T ( 1, "%ji",  I (    1));      /* { dg-warning "nul past the end" } */
+  T ( 1, "%ji",  I (   -1));      /* { dg-warning "into a region" } */
+  T ( 1, "%ji_", I (    1));      /* { dg-warning "character ._. at offset 3 past the end" } */
+  T ( 1, "_%ji", I (    1));      /* { dg-warning "into a region" } */
+  T ( 1, "_%ji_",I (    1));      /* { dg-warning "into a region" } */
+  T ( 1, "%jo",  I (    0));      /* { dg-warning "nul past the end" } */
+  T ( 1, "%ju",  I (    0));      /* { dg-warning "nul past the end" } */
+  T ( 1, "%jx",  I (    0));      /* { dg-warning "nul past the end" } */
+  T ( 1, "%#jx", I (    0));      /* { dg-warning "nul past the end" } */
+  T ( 1, "%jx",  I (    1));      /* { dg-warning "nul past the end" } */
+  T ( 1, "%#jx", I (    1));      /* { dg-warning "into a region" } */
+
+  T ( 2, "%ji",  I (    0));
+  T ( 2, "%ji",  I (    1));
+  T ( 2, "%ji",  I (    9));
+  T ( 2, "%ji",  I (   -1));      /* { dg-warning "nul past the end" } */
+  T ( 2, "%ji",  I (   10));      /* { dg-warning "nul past the end" } */
+  T ( 2, "%ji_", I (    0));      /* { dg-warning "nul past the end" } */
+  T ( 2, "_%ji", I (    0));      /* { dg-warning "nul past the end" } */
+  T ( 2, "_%ji_",I (    0));      /* { dg-warning "character ._. at offset 4 past the end" } */
+  T ( 2, "%jo",  I (    1));
+  T ( 2, "%jo",  I (    7));
+  T ( 2, "%jo",  I (  010));      /* { dg-warning "nul past the end" } */
+  T ( 2, "%jo",  I ( 0100));      /* { dg-warning "into a region" } */
+  T ( 2, "%jx",  I (    1));
+  T ( 2, "%#jx", I (    1));      /* { dg-warning "into a region" } */
+  T ( 2, "%jx",  I (  0xa));
+  T ( 2, "%jx",  I (  0xf));
+  T ( 2, "%jx",  I ( 0x10));      /* { dg-warning "nul past the end" } */
+  T ( 2, "%jx",  I ( 0xff));      /* { dg-warning "nul past the end" } */
+  T ( 2, "%jx",  I (0x1ff));      /* { dg-warning "into a region" } */
+
+  T ( 3, "%ji",  I (    0));
+  T ( 3, "%ji",  I (    1));
+  T ( 3, "%ji",  I (    9));
+  T ( 3, "%ji",  I (   -9));
+  T ( 3, "%ji",  I (   10));
+  T ( 3, "%ji",  I (   99));
+  T ( 3, "%ji",  I (  -99));      /* { dg-warning "nul past the end" } */
+
+  /* ~0 is formatted into exactly three bytes as "-1" followed by
+     the terminating NUL character.  */
+  T ( 3, "%+ji",    ~I (0));
+  T ( 3, "%-ji",    ~I (0));
+  T ( 3, "% ji",    ~I (0));
+
+  T ( 8, "%8ju",     I (1));      /* { dg-warning "nul past the end" } */
+  T ( 9, "%8ju",     I (1));
+}
+
+/* Exercise the "%ld", "%li", "%lo", "%lu", and "%lx" directives
+   with constant arguments.  */
+
+void test_sprintf_chk_l_const (void)
+{
+  T ( 1, "%li",      0L);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%li",      1L);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%li",     -1L);         /* { dg-warning "into a region" } */
+  T ( 1, "%li_",     1L);         /* { dg-warning "character ._. at offset 3 past the end" } */
+  T ( 1, "_%li",     1L);         /* { dg-warning "into a region" } */
+  T ( 1, "_%li_",    1L);         /* { dg-warning "into a region" } */
+  T ( 1, "%lo",      0L);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%lu",      0L);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%lx",      0L);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%#lx",     0L);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%lx",      1L);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%#lx",     1L);         /* { dg-warning "into a region" } */
+
+  T ( 2, "%li",      0L);
+  T ( 2, "%li",      1L);
+  T ( 2, "%li",      9L);
+  T ( 2, "%li",     -1L);         /* { dg-warning "nul past the end" } */
+  T ( 2, "%li",     10L);         /* { dg-warning "nul past the end" } */
+  T ( 2, "%li_",     0L);         /* { dg-warning "nul past the end" } */
+  T ( 2, "_%li",     0L);         /* { dg-warning "nul past the end" } */
+  T ( 2, "_%li_",    0L);         /* { dg-warning "character ._. at offset 4 past the end" } */
+  T ( 2, "%lo",      1L);
+  T ( 2, "%lo",      7L);
+  T ( 2, "%lo",    010L);         /* { dg-warning "nul past the end" } */
+  T ( 2, "%lo",   0100L);         /* { dg-warning "into a region" } */
+  T ( 2, "%lx",      1L);
+  T ( 2, "%#lx",     1L);         /* { dg-warning "into a region" } */
+  T ( 2, "%lx",    0xaL);
+  T ( 2, "%lx",    0xfL);
+  T ( 2, "%lx",   0x10L);         /* { dg-warning "nul past the end" } */
+  T ( 2, "%lx",   0xffL);         /* { dg-warning "nul past the end" } */
+  T ( 2, "%lx",  0x1ffL);         /* { dg-warning "into a region" } */
+
+  T ( 3, "%li",      0L);
+  T ( 3, "%li",      1L);
+  T ( 3, "%li",      9L);
+  T ( 3, "%li",     -9L);
+  T ( 3, "%li",     10L);
+  T ( 3, "%li",     99L);
+  T ( 3, "%li",    -99L);         /* { dg-warning "nul past the end" } */
+
+  /* ~0U is formatted into exactly three bytes as "-1" followed by
+     the terminating NUL character.  */
+  T ( 3, "%+li",   ~0LU);
+  T ( 3, "%-li",   ~0LU);
+  T ( 3, "% li",   ~0LU);
+
+  T ( 8, "%8lu",     1L);         /* { dg-warning "nul past the end" } */
+  T ( 9, "%8lu",     1L);
+}
+
+/* Exercise the "%lld", "%lli", "%llo", "%llu", and "%llx" directives
+   with constant arguments.  */
+
+void test_sprintf_chk_ll_const (void)
+{
+  T ( 1, "%lli",      0LL);     /* { dg-warning "nul past the end" } */
+  T ( 1, "%lli",      1LL);     /* { dg-warning "nul past the end" } */
+  T ( 1, "%lli",     -1LL);     /* { dg-warning "into a region" } */
+  T ( 1, "%lli_",     1LL);     /* { dg-warning "character ._. at offset 4 past the end" } */
+  T ( 1, "_%lli",     1LL);     /* { dg-warning "into a region" } */
+  T ( 1, "_%lli_",    1LL);     /* { dg-warning "into a region" } */
+  T ( 1, "%llo",      0LL);     /* { dg-warning "nul past the end" } */
+  T ( 1, "%llu",      0LL);     /* { dg-warning "nul past the end" } */
+  T ( 1, "%llx",      0LL);     /* { dg-warning "nul past the end" } */
+  T ( 1, "%#llx",     0LL);     /* { dg-warning "nul past the end" } */
+  T ( 1, "%llx",      1LL);     /* { dg-warning "nul past the end" } */
+  T ( 1, "%#llx",     1LL);     /* { dg-warning "into a region" } */
+
+  T ( 2, "%lli",      0LL);
+  T ( 2, "%lli",      1LL);
+  T ( 2, "%lli",      9LL);
+  T ( 2, "%lli",     -1LL);     /* { dg-warning "nul past the end" } */
+  T ( 2, "%lli",     10LL);     /* { dg-warning "nul past the end" } */
+  T ( 2, "%lli_",     0LL);     /* { dg-warning "nul past the end" } */
+  T ( 2, "_%lli",     0LL);     /* { dg-warning "nul past the end" } */
+  T ( 2, "_%lli_",    0LL);     /* { dg-warning "character ._. at offset 5 past the end" } */
+  T ( 2, "%llo",      1LL);
+  T ( 2, "%llo",      7LL);
+  T ( 2, "%llo",    010LL);     /* { dg-warning "nul past the end" } */
+  T ( 2, "%llo",   0100LL);     /* { dg-warning "into a region" } */
+  T ( 2, "%llx",      1LL);
+  T ( 2, "%#llx",     1LL);     /* { dg-warning "into a region" } */
+  T ( 2, "%llx",    0xaLL);
+  T ( 2, "%llx",    0xfLL);
+  T ( 2, "%llx",   0x10LL);     /* { dg-warning "nul past the end" } */
+  T ( 2, "%llx",   0xffLL);     /* { dg-warning "nul past the end" } */
+  T ( 2, "%llx",  0x1ffLL);     /* { dg-warning "into a region" } */
+
+  T ( 3, "%lli",      0LL);
+  T ( 3, "%lli",      1LL);
+  T ( 3, "%lli",      9LL);
+  T ( 3, "%lli",     -9LL);
+  T ( 3, "%lli",     10LL);
+  T ( 3, "%lli",     99LL);
+  T ( 3, "%lli",    -99LL);     /* { dg-warning "nul past the end" } */
+
+  /* ~0U is formatted into exactly three bytes as "-1" followed by
+     the terminating NUL character.  */
+  T ( 3, "%+lli",   ~0LLU);
+  T ( 3, "%-lli",   ~0LLU);
+  T ( 3, "% lli",   ~0LLU);
+
+  T ( 8, "%8llu",     1LL);     /* { dg-warning "nul past the end" } */
+  T ( 9, "%8llu",     1LL);
+
+  /* Assume 64-bit long long.  */
+#define LLONG_MAX   9223372036854775807LL   /* 19 bytes */
+#define LLONG_MIN   (-LLONG_MAX - 1)        /* 20 bytes */
+
+  T (18, "%lli", LLONG_MIN);    /* { dg-warning "into a region" } */
+  T (19, "%lli", LLONG_MIN);    /* { dg-warning "into a region" } */
+  T (20, "%lli", LLONG_MIN);    /* { dg-warning "nul past the end" } */
+  T (21, "%lli", LLONG_MIN);
+
+  T (18, "%lli", LLONG_MAX);    /* { dg-warning "into a region" } */
+  T (19, "%lli", LLONG_MAX);    /* { dg-warning "nul past the end" } */
+  T (20, "%lli", LLONG_MAX);
+
+  T (21, "%llo",      -1LL);    /* { dg-warning "into a region" } */
+  T (22, "%llo",      -1LL);    /* { dg-warning "nul past the end" } */
+  T (23, "%llo",      -1LL);
+
+  T (19, "%llu",      -1LL);    /* { dg-warning "into a region" } */
+  T (20, "%llu",      -1LL);    /* { dg-warning "nul past the end" } */
+  T (21, "%llu",      -1LL);
+
+  T (15, "%llx",      -1LL);    /* { dg-warning "into a region" } */
+  T (16, "%llx",      -1LL);    /* { dg-warning "nul past the end" } */
+  T (17, "%llx",      -1LL);
+}
+
+void test_sprintf_chk_L_const (void)
+{
+  T ( 1, "%Li",        0LL);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%Li",        1LL);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%Li",       -1LL);         /* { dg-warning "into a region" } */
+  T ( 1, "%Li_",       1LL);         /* { dg-warning "character ._. at offset 3 past the end" } */
+  T ( 1, "_%Li",       1LL);         /* { dg-warning "into a region" } */
+  T ( 1, "_%Li_",      1LL);         /* { dg-warning "into a region" } */
+}
+
+void test_sprintf_chk_z_const (void)
+{
+  T ( 1, "%zi",        (size_t)0);  /* { dg-warning "nul past the end" } */
+  T ( 1, "%zi",        (size_t)1);  /* { dg-warning "nul past the end" } */
+  T ( 1, "%zi",        (size_t)-1L);/* { dg-warning "into a region" } */
+  T ( 1, "%zi_",       (size_t)1);  /* { dg-warning "character ._. at offset 3 past the end" } */
+  T ( 1, "_%zi",       (size_t)1);  /* { dg-warning "into a region" } */
+  T ( 1, "_%zi_",      (size_t)1);  /* { dg-warning "into a region" } */
+
+  T ( 2, "%zu",        (size_t)1);
+  T ( 2, "%zu",        (size_t)9);
+  T ( 2, "%zu",        (size_t)10); /* { dg-warning "nul past the end" } */
+}
+
+void test_sprintf_chk_e_const (void)
+{
+  T  (0, "%E",   0.0);           /* { dg-warning "into a region" } */
+  T  (0, "%e",   0.0);           /* { dg-warning "into a region" } */
+  T ( 1, "%E",   1.0);           /* { dg-warning "into a region" } */
+  T ( 1, "%e",   1.0);           /* { dg-warning "into a region" } */
+  T ( 2, "%e",   2.0);           /* { dg-warning "into a region" } */
+  T ( 3, "%e",   3.0);           /* { dg-warning "into a region" } */
+  T (12, "%e",   1.2);           /* { dg-warning "nul past the end" } */
+  T (12, "%e",  12.0);           /* { dg-warning "nul past the end" } */
+  T (13, "%e",   1.3);           /* 1.300000e+00 */
+  T (13, "%E",  13.0);           /* 1.300000e+01 */
+  T (13, "%e",  13.0);
+  T (13, "%E",  1.4e+99);        /* 1.400000e+99 */
+  T (13, "%e",  1.5e+100);       /* { dg-warning "nul past the end" } */
+  T (14, "%E",  1.6e+101);       /* 1.600000E+101 */
+  T (14, "%e", -1.7e+102);       /* { dg-warning "nul past the end" } */
+  T (15, "%E", -1.8e+103);       /* -1.800000E+103 */
+
+  T (16, "%.8e", -1.9e+104);     /* { dg-warning "nul past the end" } */
+  T (17, "%.8e", -2.0e+105);     /* -2.00000000e+105 */
+
+  T ( 5, "%.0e", 0.0);           /* { dg-warning "nul past the end" } */
+  T ( 5, "%.0e", 1.0);           /* { dg-warning "nul past the end" } */
+  T ( 6, "%.0e", 1.0);
+}
+
+/* At -Wformat-length level 1 unknown numbers are assumed to have
+   the value one, and unknown strings are assumed to have a zero
+   length.  */
+
+void test_sprintf_chk_s_nonconst (int i, const char *s)
+{
+  T (0, "%s",   s);             /* { dg-warning "nul past the end" } */
+  T (1, "%s",   s);
+  T (1, "%.0s", s);
+  T (1, "%.1s", s);             /* { dg-warning "nul past the end" } */
+
+  /* The following will definitely write past the end of the buffer,
+     but since at level 1 the length of an unknown string argument
+     is assumed to be zero, it will write the terminating nul past
+     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" } */
+}
+
+/* Exercise the hh length modifier with all integer specifiers and
+   a non-constant argument.  */
+
+void test_sprintf_chk_hh_nonconst (int a)
+{
+  T (0, "%hhd",         a);     /* { dg-warning "into a region" } */
+  T (0, "%hhi",         a);     /* { dg-warning "into a region" } */
+  T (0, "%hhu",         a);     /* { dg-warning "into a region" } */
+  T (0, "%hhx",         a);     /* { dg-warning "into a region" } */
+
+  T (1, "%hhd",         a);     /* { dg-warning "nul past the end" } */
+  T (1, "%hhi",         a);     /* { dg-warning "nul past the end" } */
+  T (1, "%hhu",         a);     /* { dg-warning "nul past the end" } */
+  T (1, "%hhx",         a);     /* { dg-warning "nul past the end" } */
+
+  T (1, "% hhd",        a);     /* { dg-warning "into a region" } */
+  T (1, "% hhi",        a);     /* { dg-warning "into a region" } */
+  T (1, "%+hhd",        a);     /* { dg-warning "into a region" } */
+  T (1, "%+hhi",        a);     /* { dg-warning "into a region" } */
+  T (1, "%-hhd",        a);     /* { dg-warning "nul past the end" } */
+  T (1, "%-hhi",        a);     /* { dg-warning "nul past the end" } */
+
+  T (2, "%hhd",         a);
+  T (2, "%hhi",         a);
+  T (2, "%hho",         a);
+  T (2, "%hhu",         a);
+  T (2, "%hhx",         a);
+
+  T (2, "% hhd",        a);     /* { dg-warning "nul past the end" } */
+  T (2, "% hhi",        a);     /* { dg-warning "nul past the end" } */
+  T (2, "% hho",        a);     /* { dg-warning ". . flag used with .%o." } */
+  T (2, "% hhu",        a);     /* { dg-warning ". . flag used with .%u." } */
+  T (2, "% hhx",        a);     /* { dg-warning ". . flag used with .%x." } */
+
+  T (2, "#%hho",        a);     /* { dg-warning "nul past the end" } */
+  T (2, "#%hhx",        a);     /* { dg-warning "nul past the end" } */
+
+  T (3, "%2hhd",        a);
+  T (3, "%2hhi",        a);
+  T (3, "%2hho",        a);
+  T (3, "%2hhu",        a);
+  T (3, "%2hhx",        a);
+
+  /* Exercise cases where the type of the actual argument (whose value
+     and range are unknown) constrain the size of the output and so
+     can be used to avoid what would otherwise be false positives.  */
+
+  T (2, "%hhd", (UChar)a);
+  T (2, "%hhi", (UChar)a);
+  T (2, "%-hhi", (UChar)a);
+}
+
+/* Exercise the h length modifier with all integer specifiers and
+   a non-constant argument.  */
+
+void test_sprintf_chk_h_nonconst (int a)
+{
+  T (0, "%hd",          a);     /* { dg-warning "into a region" } */
+  T (0, "%hi",          a);     /* { dg-warning "into a region" } */
+  T (0, "%hu",          a);     /* { dg-warning "into a region" } */
+  T (0, "%hx",          a);     /* { dg-warning "into a region" } */
+
+  T (1, "%hd",          a);     /* { dg-warning "nul past the end" } */
+  T (1, "%hi",          a);     /* { dg-warning "nul past the end" } */
+  T (1, "%hu",          a);     /* { dg-warning "nul past the end" } */
+  T (1, "%hx",          a);     /* { dg-warning "nul past the end" } */
+
+  T (1, "% hd",         a);     /* { dg-warning "into a region" } */
+  T (1, "% hi",         a);     /* { dg-warning "into a region" } */
+  T (1, "%+hd",         a);     /* { dg-warning "into a region" } */
+  T (1, "%+hi",         a);     /* { dg-warning "into a region" } */
+  T (1, "%-hd",         a);     /* { dg-warning "nul past the end" } */
+  T (1, "%-hi",         a);     /* { dg-warning "nul past the end" } */
+
+  T (2, "%hd",          a);
+  T (2, "%hi",          a);
+  T (2, "%ho",          a);
+  T (2, "%hu",          a);
+  T (2, "%hx",          a);
+
+  T (2, "% hd",         a);     /* { dg-warning "nul past the end" } */
+  T (2, "% hi",         a);     /* { dg-warning "nul past the end" } */
+  T (2, "% ho",         a);     /* { dg-warning ". . flag used with .%o." } */
+  T (2, "% hu",         a);     /* { dg-warning ". . flag used with .%u." } */
+  T (2, "% hx",         a);     /* { dg-warning ". . flag used with .%x." } */
+
+  T (2, "#%ho",         a);     /* { dg-warning "nul past the end" } */
+  T (2, "#%hx",         a);     /* { dg-warning "nul past the end" } */
+
+  T (3, "%2hd",         a);
+  T (3, "%2hi",         a);
+  T (3, "%2ho",         a);
+  T (3, "%2hu",         a);
+  T (3, "%2hx",         a);
+}
+
+/* Exercise all integer specifiers with no modifier and a non-constant
+   argument.  */
+
+void test_sprintf_chk_int_nonconst (int a)
+{
+  T (0, "%d",           a);     /* { dg-warning "into a region" } */
+  T (0, "%i",           a);     /* { dg-warning "into a region" } */
+  T (0, "%u",           a);     /* { dg-warning "into a region" } */
+  T (0, "%x",           a);     /* { dg-warning "into a region" } */
+
+  T (1, "%d",           a);     /* { dg-warning "nul past the end" } */
+  T (1, "%i",           a);     /* { dg-warning "nul past the end" } */
+  T (1, "%u",           a);     /* { dg-warning "nul past the end" } */
+  T (1, "%x",           a);     /* { dg-warning "nul past the end" } */
+
+  T (1, "% d",          a);     /* { dg-warning "into a region" } */
+  T (1, "% i",          a);     /* { dg-warning "into a region" } */
+  T (1, "%+d",          a);     /* { dg-warning "into a region" } */
+  T (1, "%+i",          a);     /* { dg-warning "into a region" } */
+  T (1, "%-d",          a);     /* { dg-warning "nul past the end" } */
+  T (1, "%-i",          a);     /* { dg-warning "nul past the end" } */
+
+  T (2, "%d",           a);
+  T (2, "%i",           a);
+  T (2, "%o",           a);
+  T (2, "%u",           a);
+  T (2, "%x",           a);
+
+  T (2, "% d",          a);     /* { dg-warning "nul past the end" } */
+  T (2, "% i",          a);     /* { dg-warning "nul past the end" } */
+  T (2, "% o",          a);     /* { dg-warning ". . flag used with .%o." } */
+  T (2, "% u",          a);     /* { dg-warning ". . flag used with .%u." } */
+  T (2, "% x",          a);     /* { dg-warning ". . flag used with .%x." } */
+
+  T (2, "#%o",          a);     /* { dg-warning "nul past the end" } */
+  T (2, "#%x",          a);     /* { dg-warning "nul past the end" } */
+
+  T (3, "%2d",          a);
+  T (3, "%2i",          a);
+  T (3, "%2o",          a);
+  T (3, "%2u",          a);
+  T (3, "%2x",          a);
+}
+
+void test_sprintf_chk_e_nonconst (double d)
+{
+  /* 1.0 is formatted as "1.000000E+00" (i.e., 12 bytes).  */
+  T  (0, "%E",          d);           /* { dg-warning "writing between 12 and 14 bytes into a region of size 0" } */
+  T  (0, "%e",          d);           /* { dg-warning "into a region" } */
+  T ( 1, "%E",          d);           /* { dg-warning "into a region" } */
+  T ( 1, "%e",          d);           /* { dg-warning "into a region" } */
+  T ( 2, "%e",          d);           /* { dg-warning "into a region" } */
+  T ( 3, "%e",          d);           /* { dg-warning "into a region" } */
+  T (12, "%e",          d);           /* { dg-warning "past the end" } */
+  T (12, "%e",          d);           /* { dg-warning "past the end" } */
+  T (13, "%E",          d);           /* 1.000000E+00 */
+  T (13, "%e",          d);
+  T (14, "%E",          d);
+  T (14, "%e",          d);
+
+  /* The range of output of "%.0e" is between 5 and 7 bytes (not counting
+     the terminating NUL.  */
+  T ( 5, "%.0e",        d);           /* { dg-warning "writing a terminating nul past the end" } */
+  T ( 6, "%.0e",        d);           /* 1e+00 */
+
+  /* The range of output of "%.1e" is between 7 and 9 bytes (not counting
+     the terminating NUL.  */
+  T ( 7, "%.1e",        d);           /* { dg-warning "writing a terminating nul past the end" } */
+  T ( 8, "%.1e",        d);
+}
+
+void test_sprintf_chk_f_nonconst (double d)
+{
+  T  (0, "%F",          d);           /* { dg-warning "into a region" } */
+  T  (0, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 1, "%F",          d);           /* { dg-warning "into a region" } */
+  T ( 1, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 2, "%F",          d);           /* { dg-warning "into a region" } */
+  T ( 2, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 3, "%F",          d);           /* { dg-warning "into a region" } */
+  T ( 3, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 4, "%F",          d);           /* { dg-warning "into a region" } */
+  T ( 4, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 5, "%F",          d);           /* { dg-warning "into a region" } */
+  T ( 5, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 6, "%F",          d);           /* { dg-warning "into a region" } */
+  T ( 6, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 7, "%F",          d);           /* { dg-warning "into a region" } */
+  T ( 7, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 8, "%F",          d);           /* { dg-warning "nul past the end" } */
+  T ( 8, "%f",          d);           /* { dg-warning "nul past the end" } */
+  T ( 9, "%F",          d);
+  T ( 9, "%f",          d);
+}
+
+/* Tests for __builtin_vsprintf_chk are the same as those for
+   __builtin_sprintf_chk with non-constant arguments.  */
+#undef T
+#define T(size, fmt)							\
+  __builtin___vsprintf_chk (buffer (size), 0, objsize (size), fmt, va)
+
+void test_vsprintf_chk_c (__builtin_va_list va)
+{
+  /* Verify the full text of the diagnostic for just the distinct messages
+     and use abbreviations in subsequent test cases.  */
+  T (0, "%c");              /* { dg-warning ".%c. directive writing 1 byte into a region of size 0" } */
+  T (1, "%c");              /* { dg-warning "writing a terminating nul past the end" } */
+  T (1, "%c");              /* { dg-warning "nul past the end" } */
+  T (2, "%c");
+  T (2, "%2c");             /* { dg-warning "nul past the end" } */
+  T (2, "%3c");             /* { dg-warning "into a region" } */
+  T (2, "%c%c");            /* { dg-warning "nul past the end" } */
+  T (3, "%c%c");
+
+  /* Wide characters.  */
+  T (0, "%lc");             /* { dg-warning "nul past the end" } */
+  T (1, "%lc");
+  T (2, "%lc");
+
+  /* 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");
+  /* 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 (3, "%lc%c");
+  /* 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");
+
+}
+
+void test_vsprintf_chk_int (__builtin_va_list va)
+{
+  T (0, "%d");                /* { dg-warning "into a region" } */
+  T (0, "%i");                /* { dg-warning "into a region" } */
+  T (0, "%u");                /* { dg-warning "into a region" } */
+  T (0, "%x");                /* { dg-warning "into a region" } */
+
+  T (1, "%d");                /* { dg-warning "nul past the end" } */
+  T (1, "%i");                /* { dg-warning "nul past the end" } */
+  T (1, "%u");                /* { dg-warning "nul past the end" } */
+  T (1, "%x");                /* { dg-warning "nul past the end" } */
+
+  T (1, "% d");               /* { dg-warning "into a region" } */
+  T (1, "% i");               /* { dg-warning "into a region" } */
+  T (1, "%+d");               /* { dg-warning "into a region" } */
+  T (1, "%+i");               /* { dg-warning "into a region" } */
+  T (1, "%-d");               /* { dg-warning "nul past the end" } */
+  T (1, "%-i");               /* { dg-warning "nul past the end" } */
+
+  T (2, "%d");
+  T (2, "%i");
+  T (2, "%o");
+  T (2, "%u");
+  T (2, "%x");
+
+  T (2, "% d");               /* { dg-warning "nul past the end" } */
+  T (2, "% i");               /* { dg-warning "nul past the end" } */
+  T (2, "% o");               /* { dg-warning ". . flag used with .%o." } */
+  T (2, "% u");               /* { dg-warning ". . flag used with .%u." } */
+  T (2, "% x");               /* { dg-warning ". . flag used with .%x." } */
+
+  T (2, "#%o");               /* { dg-warning "nul past the end" } */
+  T (2, "#%x");               /* { dg-warning "nul past the end" } */
+
+  T (3, "%2d");
+  T (3, "%2i");
+  T (3, "%2o");
+  T (3, "%2u");
+  T (3, "%2x");
+}
+
+#undef T
+#define T(size, fmt, ...)						\
+  __builtin_snprintf (buffer (size), objsize (size), fmt, __VA_ARGS__)
+
+void test_snprintf_c_const (void)
+{
+  /* Verify the full text of the diagnostic for just the distinct messages
+     and use abbreviations in subsequent test cases.  */
+
+  /* A call to snprintf with a buffer of zero size is a request to determine
+     the size of output without writing anything into the destination. No
+     warning must be issued.  */
+  T (0, "%c",     0);
+  T (1, "%c",     0);            /* { dg-warning "output truncated before the last format character" } */
+  T (1, "%c",   '1');            /* { dg-warning "output truncated" } */
+  T (2, "%c",   '1');
+  T (2, "%2c",  '1');            /* { dg-warning "output truncated" } */
+  T (2, "%3c",  '1');            /* { dg-warning "directive output truncated" } */
+  T (2, "%c%c", '1', '2');       /* { dg-warning "output truncated" } */
+  T (3, "%c%c", '1', '2');
+
+  /* Wide characters.  */
+  T (0, "%lc",  0);
+  T (1, "%lc",  0);
+  T (2, "%lc",  0);
+
+  /* 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",  L'1');
+  T (2, "%1lc", L'1');
+  /* Writing at least 1 characted into a field two characters wide.  */
+  T (2, "%2lc", L'1');          /* { dg-warning "output truncated before the last format character" } */
+
+  T (3, "%lc%c",   L'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", L'1', '2', '3');   /* { dg-warning "output truncated" } */
+  T (3, "%lc%lc%c", L'1', L'2', '3'); /* { dg-warning "output truncated" } */
+}
+
+#undef T
+#define T(size, fmt, ...)						\
+  __builtin___snprintf_chk (buffer (size), objsize (size),		\
+			    0, objsize (size), fmt, __VA_ARGS__)
+
+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 "always overflow|specified size 3 exceeds the size 2 of the destination" } */
+
+  T (0, "%c",     0);
+  T (0, "%c%c",   0, 0);
+  T (0, "%c_%c",  0, 0);
+  T (0, "_%c_%c", 0, 0);
+
+  T (1, "%c",     0);            /* { dg-warning "output truncated before the last format character" } */
+  T (1, "%c",   '1');            /* { dg-warning "output truncated" } */
+  T (2, "%c",   '1');
+  T (2, "%2c",  '1');            /* { dg-warning "output truncated" } */
+  T (2, "%3c",  '1');            /* { dg-warning "directive output truncated" } */
+  T (2, "%c%c", '1', '2');       /* { dg-warning "output truncated before the last format character" } */
+  T (3, "%c%c", '1', '2');
+  T (3, "%c_%c", '1', '2');      /* { dg-warning "output truncated" } */
+
+  /* Wide characters.  */
+  T (0, "%lc",  0);
+  T (1, "%lc",  0);
+  T (2, "%lc",  0);
+
+  /* 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",  L'1');
+  T (2, "%1lc", L'1');
+  /* Writing at least 1 characted into a field two characters wide.  */
+  T (2, "%2lc", L'1');          /* { dg-warning "output truncated before the last format character" } */
+
+  T (3, "%lc%c",   L'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", L'1', '2', '3');   /* { dg-warning "output truncated" } */
+  T (3, "%lc%lc%c", L'1', L'2', '3'); /* { dg-warning "output truncated" } */
+}
+
+/* Macro to verify that calls to __builtin_vsprintf (i.e., with no size
+   argument) issue diagnostics by correctly determining the size of
+   the destination buffer.  */
+#undef T
+#define T(size, fmt)				\
+  __builtin_vsprintf (buffer (size), fmt, va)
+
+void test_vsprintf_s (__builtin_va_list va)
+{
+  T (0, "%s");              /* { dg-warning "writing a terminating nul past the end" } */
+  T (1, "%s");
+  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" } */
+}
+
+/* Exercise all integer specifiers with no modifier and a non-constant
+   argument.  */
+
+void test_vsprintf_int (__builtin_va_list va)
+{
+  T (0, "%d");     /* { dg-warning "into a region" } */
+  T (0, "%i");     /* { dg-warning "into a region" } */
+  T (0, "%u");     /* { dg-warning "into a region" } */
+  T (0, "%x");     /* { dg-warning "into a region" } */
+
+  T (1, "%d");     /* { dg-warning "nul past the end" } */
+  T (1, "%i");     /* { dg-warning "nul past the end" } */
+  T (1, "%u");     /* { dg-warning "nul past the end" } */
+  T (1, "%x");     /* { dg-warning "nul past the end" } */
+
+  T (1, "% d");     /* { dg-warning "into a region" } */
+  T (1, "% i");     /* { dg-warning "into a region" } */
+  T (1, "%+d");     /* { dg-warning "into a region" } */
+  T (1, "%+i");     /* { dg-warning "into a region" } */
+  T (1, "%-d");     /* { dg-warning "nul past the end" } */
+  T (1, "%-i");     /* { dg-warning "nul past the end" } */
+
+  T (2, "%d");
+  T (2, "%i");
+  T (2, "%o");
+  T (2, "%u");
+  T (2, "%x");
+
+  T (2, "% d");     /* { dg-warning "nul past the end" } */
+  T (2, "% i");     /* { dg-warning "nul past the end" } */
+  T (2, "% o");     /* { dg-warning ". . flag used with .%o." } */
+  T (2, "% u");     /* { dg-warning ". . flag used with .%u." } */
+  T (2, "% x");     /* { dg-warning ". . flag used with .%x." } */
+
+  T (2, "#%o");     /* { dg-warning "nul past the end" } */
+  T (2, "#%x");     /* { dg-warning "nul past the end" } */
+
+  T (3, "%2d");
+  T (3, "%2i");
+  T (3, "%2o");
+  T (3, "%2u");
+  T (3, "%2x");
+}
+
+#undef T
+#define T(size, fmt)							\
+  __builtin_vsnprintf (buffer (size), objsize (size), fmt, va)
+
+void test_vsnprintf_s (__builtin_va_list va)
+{
+  T (0, "%s");
+  T (1, "%s");
+  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" } */
+}
+
+#undef T
+#define T(size, fmt)							\
+  __builtin___vsnprintf_chk (buffer (size), objsize (size),		\
+			     0, objsize (size), fmt, va)
+
+void test_vsnprintf_chk_s (__builtin_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___snprintf_chk (buffer, 123, 0, 122, " ");   /* { dg-warning "always overflow|specified size 123 exceeds the size 122 of the destination object" } */
+
+  __builtin___snprintf_chk (buffer, __SIZE_MAX__, 0, 2, " ");   /* { dg-warning "always overflow|destination size .\[0-9\]+. too large" } */
+
+  T (0, "%s");
+  T (1, "%s");
+  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" } */
+}
diff --git a/gcc/testsuite/gcc.dg/format/c99-sprintf-length-1.s b/gcc/testsuite/gcc.dg/format/c99-sprintf-length-1.s
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/format/c99-sprintf-length-2.c b/gcc/testsuite/gcc.dg/format/c99-sprintf-length-2.c
new file mode 100644
index 0000000..b033aed
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/format/c99-sprintf-length-2.c
@@ -0,0 +1,195 @@ 
+/* { dg-do compile } */
+/* { dg-options "-std=c99 -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
+   below make use of LINE to avoid warnings for other lines.  */
+#ifndef LINE
+# define LINE 0
+#endif
+
+char buffer [256];
+extern char *ptr;
+
+#define buffer(size)							\
+  (!LINE || __LINE__ == LINE ? buffer + sizeof buffer - size : ptr)
+
+#define objsize(size)  (!LINE || __LINE__ == LINE ? size : __SIZE_MAX__ / 2)
+
+typedef __SIZE_TYPE__ size_t;
+
+#if !__cplusplus
+typedef __WCHAR_TYPE__ wchar_t;
+#endif
+
+typedef unsigned char UChar;
+
+#define T(size, fmt, ...)				\
+  __builtin_sprintf (buffer (size), fmt, __VA_ARGS__)
+
+__builtin_va_list va;
+
+/* Exercise buffer overflow detection with const string arguments.  */
+
+void test_s_const (void)
+{
+    /* Wide string literals are handled slightly differently than
+       at level 1.  At level 1, each wide character is assumed to
+       convert into a single byte.  At level 2, they are assumed
+       to convert into at least one byte.  */
+  T (0, "%ls",      L"");       /* { dg-warning "nul past the end" } */
+  T (1, "%ls",      L"");
+  T (1, "%ls",      L"\0");
+  T (1, "%1ls",     L"");       /* { dg-warning "nul past the end" } */
+
+  T (0, "%*ls",  0, L"");       /* { dg-warning "nul past the end" } */
+  T (1, "%*ls",  0, L"");
+  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" } */
+  T (1, "%.0ls",    L"1");
+  T (2, "%.0ls",    L"1");
+  T (2, "%.1ls",    L"1");
+
+  /* The "%.2ls" directive below will write at a minimum 1 byte (because
+     L"1" is known and can be assumed to convert to at least one multibyte
+     character), and at most 2 bytes because of the precision.  Since its
+     output is explicitly bounded it is diagnosed.  */
+  T (2, "%.2ls",    L"1");      /* { dg-warning "nul past the end" } */
+  T (2, "%.*ls", 2, L"1");      /* { dg-warning "nul past the end" } */
+
+  /* The following three are constrained by the precision to at most
+     that many bytes of the converted wide string plus a terminating NUL.  */
+  T (2, "%.0ls",    L"1");
+  T (2, "%.1ls",    L"1");
+  T (3, "%.2ls",    L"1");
+}
+
+
+struct Arrays {
+  char a1 [1];
+  char a2 [2];
+  char a3 [3];
+  char a4 [4];
+};
+
+/* Exercise buffer overflow detection with non-const string arguments.  */
+
+void test_s_nonconst (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, "%.0s", s);
+  T (1, "%.1s", s);             /* { dg-warning "writing a terminating nul" } */
+
+  T (1, "%ls",  ws);            /* { dg-warning "writing 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);
+  T (1, "%-s", a->a2);          /* { dg-warning "may write a terminating nul" } */
+  T (1, "%-s", a->a3);          /* { dg-warning "writing between 0 and 2 bytes into a region of size 1" } */
+}
+
+  /* Exercise buffer overflow detection with non-const integer arguments.  */
+
+void test_hh_nonconst (int x)
+{
+  T (1, "%hhi",         x);     /* { dg-warning "into a region" } */
+  T (2, "%hhi",         x);     /* { dg-warning "into a region" } */
+  T (3, "%hhi",         x);     /* { dg-warning "into a region" } */
+  T (4, "%hhi",         x);     /* { dg-warning "may write a terminating nul past the end of the destination" } */
+}
+
+void test_h_nonconst (int x)
+{
+  extern UChar uc;
+
+  T (1, "%hi",         uc);     /* { dg-warning "into a region" } */
+  T (2, "%hi",         uc);     /* { dg-warning "into a region" } */
+  /* Formatting an 8-bit unsigned char as a signed short (or any other
+     type with greater precision) can write at most 3 characters.  */
+  T (3, "%hi",         uc);     /* { dg-warning "terminating nul past" } */
+  T (4, "%hi",         uc);
+
+  /* Verify that the same thing works when the int argument is cast
+     to unsigned char.  */
+  T (1, "%hi",   (UChar)x);     /* { dg-warning "into a region" } */
+  T (2, "%hi",   (UChar)x);     /* { dg-warning "into a region" } */
+  T (3, "%hi",   (UChar)x);     /* { dg-warning "may write a terminating nul past the end of the destination" } */
+  T (4, "%hi",   (UChar)x);
+}
+
+void test_i_nonconst (int x)
+{
+  extern UChar uc;
+
+  T (1, "%i",          uc);     /* { dg-warning "into a region" } */
+  T (2, "%i",          uc);     /* { dg-warning "into a region" } */
+  T (3, "%i",          uc);     /* { dg-warning "terminating nul past" } */
+  T (4, "%i",          uc);
+
+  T (1, "%i",    (UChar)x);     /* { dg-warning "into a region" } */
+  T (2, "%i",    (UChar)x);     /* { dg-warning "into a region" } */
+  T (3, "%i",    (UChar)x);     /* { dg-warning "terminating nul past" } */
+  T (4, "%i",    (UChar)x);
+
+  /* Verify the same thing using a bit-field.  */
+  extern struct {
+    unsigned int  b1: 1;
+    unsigned int  b2: 2;
+    unsigned int  b3: 3;
+    unsigned int  b4: 4;
+             int sb4: 4;
+    unsigned int  b5: 5;
+    unsigned int  b6: 6;
+    unsigned int  b7: 7;
+    unsigned int  b8: 8;
+  } bf, abf[], *pbf;
+
+  T (1, "%i",       bf.b1);     /* { dg-warning "nul past the end" } */
+  T (1, "%i",  abf [x].b1);     /* { dg-warning "nul past the end" } */
+  T (1, "%i",     pbf->b1);     /* { dg-warning "nul past the end" } */
+  /* A one bit bit-field can only be formatted as '0' or '1'.  Similarly,
+     two- and three-bit bit-fields can only be formatted as a single
+     decimal digit.  */
+  T (2, "%i",       bf.b1);
+  T (2, "%i",  abf [x].b1);
+  T (2, "%i",     pbf->b1);
+  T (2, "%i",       bf.b2);
+  T (2, "%i",  abf [x].b2);
+  T (2, "%i",     pbf->b2);
+  T (2, "%i",       bf.b3);
+  T (2, "%i",  abf [x].b3);
+  T (2, "%i",     pbf->b3);
+  /* A four-bit bit-field can be formatted as either one or two digits.  */
+  T (2, "%i",       bf.b4);     /* { dg-warning "nul past the end" } */
+  T (2, "%i",  abf [x].b4);     /* { dg-warning "nul past the end" } */
+  T (2, "%i",     pbf->b4);     /* { dg-warning "nul past the end" } */
+
+  T (3, "%i",       bf.b4);
+  T (3, "%i",     pbf->b4);
+  T (3, "%i",       bf.b5);
+  T (3, "%i",     pbf->b5);
+  T (3, "%i",       bf.b6);
+  T (3, "%i",     pbf->b6);
+  T (3, "%i",       bf.b7);     /* { dg-warning "nul past the end" } */
+  T (3, "%i",     pbf->b7);     /* { dg-warning "nul past the end" } */
+
+  T (1, "%i",       bf.b8);     /* { dg-warning "into a region" } */
+  T (2, "%i",       bf.b8);     /* { dg-warning "into a region" } */
+  /* Formatting an 8-bit unsigned char as a signed short (or any other
+     type with greater precision) int can write at most 3 characters.  */
+  T (3, "%i",       bf.b8);     /* { dg-warning "terminating nul past" } */
+  T (4, "%i",       bf.b8);
+
+  T (1, "%i",       bf.b8);     /* { dg-warning "into a region" } */
+  T (2, "%i",       bf.b8);     /* { dg-warning "into a region" } */
+  T (3, "%i",       bf.b8);     /* { dg-warning "terminating nul past" } */
+
+  T (2, "%i",      bf.sb4);     /* { dg-warning "terminating nul past" } */
+  T (3, "%i",      bf.sb4);
+  T (4, "%i",      bf.sb4);
+}
diff --git a/gcc/testsuite/gcc.dg/format/c99-sprintf-length-2.s b/gcc/testsuite/gcc.dg/format/c99-sprintf-length-2.s
new file mode 100644
index 0000000..e69de29
diff --git a/gcc/testsuite/gcc.dg/format/c99-sprintf-length-opt.c b/gcc/testsuite/gcc.dg/format/c99-sprintf-length-opt.c
new file mode 100644
index 0000000..b6da8f0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/format/c99-sprintf-length-opt.c
@@ -0,0 +1,223 @@ 
+/* { dg-do compile } */
+/* { dg-options "-std=c99 -O2 -Wformat -Wformat-length=1 -ftrack-macro-expansion=0" } */
+
+#ifndef LINE
+#  define LINE 0
+#endif
+
+#define bos(x)  \
+  ((!LINE || __LINE__ == LINE) ? __builtin_object_size (x, 0) : __SIZE_MAX__ / 2)
+
+#define T(bufsize, fmt, ...)						\
+  do {									\
+    char *d = (char *)__builtin_malloc (bufsize);			\
+    __builtin___sprintf_chk (d, 0, bos (d), fmt, __VA_ARGS__);		\
+    sink (d);								\
+  } while (0)
+
+void
+sink (void*);
+
+/* 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 x (void);
+
+/* 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, const char *t)
+{
+#define x x ()
+
+  T (1, "%s", x ? "" : "1");       /* { dg-warning "nul past the end" } */
+  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 (2, "%s", x ? "" : "1");
+  T (2, "%s", x ? "" : s);
+  T (2, "%s", x ? "1" : "");
+  T (2, "%s", x ? s : "");
+  T (2, "%s", x ? "1" : "2");
+  T (2, "%s", x ? "" : "12");      /* { dg-warning "nul past the end" } */
+  T (2, "%s", x ? "12" : "");      /* { dg-warning "nul past the end" } */
+
+  T (2, "%s", x ? "" : "123");     /* { dg-warning "into a region" } */
+  T (2, "%s", x ? "123" : "");     /* { dg-warning "into a region" } */
+
+#undef x
+}
+
+
+/* Verify that the checker makes use of integer constant propagation
+   to detect buffer overflow in non-constant cases.  */
+
+void test_sprintf_chk_integer_value (void)
+{
+  T ( 1, "%i",  i (    0));         /* { dg-warning "nul past the end" } */
+  T ( 1, "%i",  i (    1));         /* { dg-warning "nul past the end" } */
+  T ( 1, "%i",  i (   -1));         /* { dg-warning "into a region" } */
+  T ( 1, "%i_", i (    1));         /* { dg-warning "character ._. at offset 2 past the end" } */
+  T ( 1, "_%i", i (    1));         /* { dg-warning "into a region" } */
+  T ( 1, "_%i_",i (    1));         /* { dg-warning "into a region" } */
+  T ( 1, "%o",  i (    0));         /* { dg-warning "nul past the end" } */
+  T ( 1, "%u",  i (    0));         /* { dg-warning "nul past the end" } */
+  T ( 1, "%x",  i (    0));         /* { dg-warning "nul past the end" } */
+  T ( 1, "%#x", i (    0));         /* { dg-warning "nul past the end" } */
+  T ( 1, "%x",  i (    1));         /* { dg-warning "nul past the end" } */
+  T ( 1, "%#x", i (    1));         /* { dg-warning "into a region" } */
+
+  T ( 2, "%i",  i (    0));
+  T ( 2, "%i",  i (    1));
+  T ( 2, "%i",  i (    9));
+  T ( 2, "%i",  i (   -1));         /* { dg-warning "nul past the end" } */
+  T ( 2, "%i",  i (   10));         /* { dg-warning "nul past the end" } */
+  T ( 2, "%i_", i (    0));         /* { dg-warning "nul past the end" } */
+  T ( 2, "_%i", i (    0));         /* { dg-warning "nul past the end" } */
+  T ( 2, "_%i_",i (    0));         /* { dg-warning "character ._. at offset 3 past the end" } */
+  T ( 2, "%o",  i (    1));
+  T ( 2, "%o",  i (    7));
+  T ( 2, "%o",  i (  010));         /* { dg-warning "nul past the end" } */
+  T ( 2, "%o",  i ( 0100));         /* { dg-warning "into a region" } */
+  T ( 2, "%x",  i (    1));
+  T ( 2, "%#x", i (    1));         /* { dg-warning "into a region" } */
+  T ( 2, "%x",  i (  0xa));
+  T ( 2, "%x",  i (  0xf));
+  T ( 2, "%x",  i ( 0x10));         /* { dg-warning "nul past the end" } */
+  T ( 2, "%x",  i ( 0xff));         /* { dg-warning "nul past the end" } */
+  T ( 2, "%x",  i (0x1ff));         /* { dg-warning "into a region" } */
+
+  T ( 3, "%i",  i (    0));
+  T ( 3, "%i",  i (    1));
+  T ( 3, "%i",  i (    9));
+  T ( 3, "%i",  i (   -9));
+  T ( 3, "%i",  i (   10));
+  T ( 3, "%i",  i (   99));
+  T ( 3, "%i",  i (  -99));         /* { dg-warning "nul past the end" } */
+
+  T ( 3, "%i",  i (99) + i (1));    /* { dg-warning "nul past the end" } */
+
+  T ( 8, "%8u", i (    1));         /* { dg-warning "nul past the end" } */
+  T ( 9, "%8u", i (    1));
+}
+
+/* Functions to require optimization to figure out the range of the operand.
+   Used to verify that the checker makes use of the range information to
+   avoid diagnosing the output of sufficiently constrained arguments to
+   integer directives.  */
+
+signed char*
+range_schar (signed char *val, signed char min, signed char max)
+{
+  if (*val < min || max < *val) __builtin_abort ();
+  return val;
+}
+
+unsigned char*
+range_uchar (unsigned char *val, unsigned char min, unsigned char max)
+{
+  if (*val < min || max < *val) __builtin_abort ();
+  return val;
+}
+
+signed short*
+range_sshort (signed short *val, signed short min, signed short max)
+{
+  if (*val < min || max < *val) __builtin_abort ();
+  return val;
+}
+
+unsigned short*
+range_ushort (unsigned short *val, unsigned short min, unsigned short max)
+{
+  if (*val < min || max < *val) __builtin_abort ();
+  return val;
+}
+
+static int idx;
+
+/* Exercise ranges only in types signed and unsigned char and short.
+   No other types work due to bug 71690.  */
+
+void test_sprintf_chk_range_schar (signed char *a)
+{
+  /* Ra creates a range of signed char for A [idx].  A different
+     value is used each time to prevent the ranges from intesecting
+     one another, possibly even eliminating some tests as a result
+     of the range being empty. */
+#define R(min, max) *range_schar (a + idx++, min, max)
+
+  T ( 0, "%i",  R (0, 9));      /* { dg-warning ".%i. directive writing 1 byte into a region of size 0" } */
+  T ( 1, "%i",  R (0, 9));      /* { dg-warning "nul past the end" } */
+  T ( 2, "%i",  R (0, 9));
+  T ( 2, "%i",  R (-1, 0));     /* { dg-warning "may write a terminating nul past the end of the destination" } */
+  T ( 2, "%i",  R (9, 10));     /* { dg-warning "may write a terminating nul past the end of the destination" } */
+
+  T ( 3, "%i",  R ( -9,   9));
+  T ( 3, "%i",  R (-99,  99));  /* { dg-warning "may write a terminating nul past the end of the destination" } */
+  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.
+     It's 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 ".%i. directive writing between 1 and 2 bytes into a region of size 1" } */
+
+  T ( 4, "%i%i", R (10, 11), R (12, 13));   /* { dg-warning "nul past the end" } */
+
+  T ( 5, "%i%i", R (-9, 99), R (-9, 99));
+
+  T ( 6, "%i_%i_%i", R (0, 9), R (0, 9), R (0,  9));
+  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 ".%i. directive writing between 1 and 2 bytes into a region of size 1" } */
+}
+
+void test_sprintf_chk_range_uchar (unsigned char *a, unsigned char *b)
+{
+#undef Ra
+#define Ra(min, max) *range_uchar (a + idx++, min, max)
+
+  T ( 0, "%i",  Ra (0,  9));   /* { dg-warning ".%i. directive writing 1 byte into a region of size 0" } */
+  T ( 1, "%i",  Ra (0,  9));   /* { dg-warning "nul past the end" } */
+  T ( 2, "%i",  Ra (0,  9));
+  T ( 2, "%i",  Ra (9, 10));   /* { dg-warning "may write a terminating nul past the end of the destination" } */
+
+  T ( 3, "%i",  Ra (0,  99));
+  T ( 3, "%i",  Ra (0, 100));  /* { dg-warning "may write a terminating nul past the end of the destination" } */
+}
+
+void test_sprintf_chk_range_sshort (signed short *a, signed short *b)
+{
+#undef Ra
+#define Ra(min, max) *range_sshort (a + idx++, min, max)
+
+  T ( 0, "%i",  Ra ( 0, 9));     /* { dg-warning ".%i. directive writing 1 byte into a region of size 0" } */
+  T ( 1, "%i",  Ra ( 0, 1));     /* { dg-warning "nul past the end" } */
+  T ( 1, "%i",  Ra ( 0, 9));     /* { dg-warning "nul past the end" } */
+  T ( 2, "%i",  Ra ( 0, 1));
+  T ( 2, "%i",  Ra ( 8, 9));
+  T ( 2, "%i",  Ra ( 0, 9));
+  T ( 2, "%i",  Ra (-1, 0));     /* { dg-warning "may write a terminating nul past the end of the destination" } */
+  T ( 2, "%i",  Ra ( 9, 10));    /* { dg-warning "may write a terminating nul past the end of the destination" } */
+
+  T ( 3, "%i",  Ra ( 0, 99));
+  T ( 3, "%i",  Ra (99, 999));   /* { dg-warning "may write a terminating nul past the end of the destination" } */
+
+  T ( 4, "%i",  Ra (  0,  999));
+  T ( 4, "%i",  Ra ( 99,  999));
+  T ( 4, "%i",  Ra (998,  999));
+  T ( 4, "%i",  Ra (999, 1000)); /* { dg-warning "may write a terminating nul past the end of the destination" } */
+}
diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h
index 36299a6..5728d3f 100644
--- a/gcc/tree-pass.h
+++ b/gcc/tree-pass.h
@@ -469,6 +469,7 @@  extern simple_ipa_opt_pass *make_pass_ipa_oacc (gcc::context *ctxt);
 extern simple_ipa_opt_pass *make_pass_ipa_oacc_kernels (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_gen_hsail (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_warn_nonnull_compare (gcc::context *ctxt);
+extern gimple_opt_pass *make_pass_sprintf_length (gcc::context *ctxt);
 
 /* IPA Passes */
 extern simple_ipa_opt_pass *make_pass_ipa_lower_emutls (gcc::context *ctxt);