diff mbox

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

Message ID 5776B33E.2080504@gmail.com
State New
Headers show

Commit Message

Martin Sebor July 1, 2016, 6:15 p.m. UTC
The attached patch enhances compile-time checking for buffer overflow
and output truncation in non-trivial calls to the sprintf family of
functions under a new option -Wformat-length=[12].  This initial
patch handles printf directives with string, integer, and simple
floating arguments but eventually I'd like to extend it all other
functions and directives for which it makes sense.

I made some choices in the implementation that resulted in trade-offs
in the quality of the diagnostics.  I would be grateful for comments
and suggestions how to improve them.  Besides the list I include
Jakub who already gave me some feedback (thanks), Joseph who as
I understand has deep knowledge of the c-format.c code, and Richard
for his input on the LTO concern below.

1) Making use of -Wformat machinery in c-family/c-format.c.  This
    seemed preferable to duplicating some of the same code elsewhere
    (I initially started implementing it in expand_builtin in
    builtins.c).  It makes the implementation readily extensible
    to all the same formats as those already handled for -Wformat.
    One drawback is that unlike in expand_builtin, calls to these
    functions cannot readily be folded.  Another drawback pointed
    out by Jakub is that since the code is only available in the
    C and C++ compilers, it apparently may not be available with
    an LTO compiler (I don't completely understand this problem
    but I mention it in the interest of full disclosure). In light
    of the dependency in (2) below, I don't see a way to avoid it
    (moving c-format.c to the middle end was suggested but seemed
    like too much of a change to me).

2) Optimization.
    In keeping with the other -Wformat options, the checking is
    enabled without optimization.  Especially at level 2, the
    warnings can be useful even without it.  But to make buffer
    sizes and non-constant argument values available in calls to
    functions like sprintf (via __builtin_object_size) better
    results are obtained with optimization.

3) Truncation warnings.
    Although calls to bounded functions like snprintf aren't subject
    to buffer overflow, they can be subject to accidental truncation
    when the destination buffer isn't sized appropriately.  With the
    patch, such calls are diagnosed under the same option, but I
    wonder if have a separate warning option for them might be
    preferable (e.g., -Wformat-trunc=[01] or something like that).
    Independently, it might be useful to differentiate between
    truncating calls that check the return value and those that
    don't.

Besides the usual testing I compiled several packages with the
warning.  If found a few bugs in boundary cases in Binutils that
are being fixed.

Thanks
Martin

PS There are a few FIXME notes in the patch that I will either
fix or remove, depending on feedback, before committing the
patch.

Comments

Richard Biener July 4, 2016, 10:59 a.m. UTC | #1
On Fri, 1 Jul 2016, Martin Sebor wrote:

> The attached patch enhances compile-time checking for buffer overflow
> and output truncation in non-trivial calls to the sprintf family of
> functions under a new option -Wformat-length=[12].  This initial
> patch handles printf directives with string, integer, and simple
> floating arguments but eventually I'd like to extend it all other
> functions and directives for which it makes sense.
> 
> I made some choices in the implementation that resulted in trade-offs
> in the quality of the diagnostics.  I would be grateful for comments
> and suggestions how to improve them.  Besides the list I include
> Jakub who already gave me some feedback (thanks), Joseph who as
> I understand has deep knowledge of the c-format.c code, and Richard
> for his input on the LTO concern below.
> 
> 1) Making use of -Wformat machinery in c-family/c-format.c.  This
>    seemed preferable to duplicating some of the same code elsewhere
>    (I initially started implementing it in expand_builtin in
>    builtins.c).  It makes the implementation readily extensible
>    to all the same formats as those already handled for -Wformat.
>    One drawback is that unlike in expand_builtin, calls to these
>    functions cannot readily be folded.  Another drawback pointed

folded?  You mean this -W option changes code generation?

>    out by Jakub is that since the code is only available in the
>    C and C++ compilers, it apparently may not be available with
>    an LTO compiler (I don't completely understand this problem
>    but I mention it in the interest of full disclosure). In light
>    of the dependency in (2) below, I don't see a way to avoid it
>    (moving c-format.c to the middle end was suggested but seemed
>    like too much of a change to me).

Yes, lto1 is not linked with C_COMMON_OBJS (that could be changed
of course at the expense of dragging in some dead code).  Moving
all the format stuff to the middle-end (or separated better so
the overhead in lto1 is lower) would be possible as well.

That said, a langhook as you add it highlights the issue with LTO.

Richard.

> 2) Optimization.
>    In keeping with the other -Wformat options, the checking is
>    enabled without optimization.  Especially at level 2, the
>    warnings can be useful even without it.  But to make buffer
>    sizes and non-constant argument values available in calls to
>    functions like sprintf (via __builtin_object_size) better
>    results are obtained with optimization.
> 
> 3) Truncation warnings.
>    Although calls to bounded functions like snprintf aren't subject
>    to buffer overflow, they can be subject to accidental truncation
>    when the destination buffer isn't sized appropriately.  With the
>    patch, such calls are diagnosed under the same option, but I
>    wonder if have a separate warning option for them might be
>    preferable (e.g., -Wformat-trunc=[01] or something like that).
>    Independently, it might be useful to differentiate between
>    truncating calls that check the return value and those that
>    don't.
> 
> Besides the usual testing I compiled several packages with the
> warning.  If found a few bugs in boundary cases in Binutils that
> are being fixed.
> 
> Thanks
> Martin
> 
> PS There are a few FIXME notes in the patch that I will either
> fix or remove, depending on feedback, before committing the
> patch.
>
Martin Sebor July 4, 2016, 4:23 p.m. UTC | #2
On 07/04/2016 04:59 AM, Richard Biener wrote:
> On Fri, 1 Jul 2016, Martin Sebor wrote:
>
>> The attached patch enhances compile-time checking for buffer overflow
>> and output truncation in non-trivial calls to the sprintf family of
>> functions under a new option -Wformat-length=[12].  This initial
>> patch handles printf directives with string, integer, and simple
>> floating arguments but eventually I'd like to extend it all other
>> functions and directives for which it makes sense.
>>
>> I made some choices in the implementation that resulted in trade-offs
>> in the quality of the diagnostics.  I would be grateful for comments
>> and suggestions how to improve them.  Besides the list I include
>> Jakub who already gave me some feedback (thanks), Joseph who as
>> I understand has deep knowledge of the c-format.c code, and Richard
>> for his input on the LTO concern below.
>>
>> 1) Making use of -Wformat machinery in c-family/c-format.c.  This
>>     seemed preferable to duplicating some of the same code elsewhere
>>     (I initially started implementing it in expand_builtin in
>>     builtins.c).  It makes the implementation readily extensible
>>     to all the same formats as those already handled for -Wformat.
>>     One drawback is that unlike in expand_builtin, calls to these
>>     functions cannot readily be folded.  Another drawback pointed
>
> folded?  You mean this -W option changes code generation?

No, it doesn't.  What I meant is that the same code, when added
in builtins.c instead, could readily be extended to fold into
strings expressions like

   sprintf (buf, "%i", 123);

>
>>     out by Jakub is that since the code is only available in the
>>     C and C++ compilers, it apparently may not be available with
>>     an LTO compiler (I don't completely understand this problem
>>     but I mention it in the interest of full disclosure). In light
>>     of the dependency in (2) below, I don't see a way to avoid it
>>     (moving c-format.c to the middle end was suggested but seemed
>>     like too much of a change to me).
>
> Yes, lto1 is not linked with C_COMMON_OBJS (that could be changed
> of course at the expense of dragging in some dead code).  Moving
> all the format stuff to the middle-end (or separated better so
> the overhead in lto1 is lower) would be possible as well.
>
> That said, a langhook as you add it highlights the issue with LTO.

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?

FWIW, I would expect a good number of other warnings to benefit
from optimization and having a general solution for this problem
to be helpful.  I also suspect this isn't the first time this
issue has come up.  I'm wondering what solutions have already
been considered and with what pros and cons (naively, I would
think that factoring the relevant code out of cc1 into a shared
library that lto1 could load should work).

Martin
Jakub Jelinek July 4, 2016, 4:44 p.m. UTC | #3
On Mon, Jul 04, 2016 at 10:23:06AM -0600, Martin Sebor wrote:
> >>1) Making use of -Wformat machinery in c-family/c-format.c.  This
> >>    seemed preferable to duplicating some of the same code elsewhere
> >>    (I initially started implementing it in expand_builtin in
> >>    builtins.c).  It makes the implementation readily extensible
> >>    to all the same formats as those already handled for -Wformat.
> >>    One drawback is that unlike in expand_builtin, calls to these
> >>    functions cannot readily be folded.  Another drawback pointed
> >
> >folded?  You mean this -W option changes code generation?
> 
> No, it doesn't.  What I meant is that the same code, when added
> in builtins.c instead, could readily be extended to fold into
> strings expressions like
> 
>   sprintf (buf, "%i", 123);

I've commented in some PR a few years ago that I'm not convinced we want to
do it, or at least not without careful considerations, consider .rodata
size.  Say if the user has in 1000x different places
sprintf (buf, "foobarbaz %i", NNN); for various values of NNN, then such "optimization" would replace
a single string literal of length 13 bytes with 1000 string literals of 12-20 bytes.
Consider larger string literal, with %s and long additions and it might not
be a win even for 2 occurrences.

	Jakub
Bernd Schmidt July 4, 2016, 6:10 p.m. UTC | #4
On 07/04/2016 06:44 PM, Jakub Jelinek wrote:
> On Mon, Jul 04, 2016 at 10:23:06AM -0600, Martin Sebor wrote:

>> No, it doesn't.  What I meant is that the same code, when added
>> in builtins.c instead, could readily be extended to fold into
>> strings expressions like
>>
>>   sprintf (buf, "%i", 123);
>
> I've commented in some PR a few years ago that I'm not convinced we want to
> do it, or at least not without careful considerations, consider .rodata
> size.  Say if the user has in 1000x different places
> sprintf (buf, "foobarbaz %i", NNN); for various values of NNN, then such "optimization" would replace
> a single string literal of length 13 bytes with 1000 string literals of 12-20 bytes.
> Consider larger string literal, with %s and long additions and it might not
> be a win even for 2 occurrences.

I think that's not a highly ligkely scenario, and it would still be a 
massive speed optimization over calling sprintf. Each such call is 
likely to be larger than the string literal anyway.


Bernd
Martin Sebor July 4, 2016, 6:17 p.m. UTC | #5
On 07/04/2016 10:44 AM, Jakub Jelinek wrote:
> On Mon, Jul 04, 2016 at 10:23:06AM -0600, Martin Sebor wrote:
>>>> 1) Making use of -Wformat machinery in c-family/c-format.c.  This
>>>>     seemed preferable to duplicating some of the same code elsewhere
>>>>     (I initially started implementing it in expand_builtin in
>>>>     builtins.c).  It makes the implementation readily extensible
>>>>     to all the same formats as those already handled for -Wformat.
>>>>     One drawback is that unlike in expand_builtin, calls to these
>>>>     functions cannot readily be folded.  Another drawback pointed
>>>
>>> folded?  You mean this -W option changes code generation?
>>
>> No, it doesn't.  What I meant is that the same code, when added
>> in builtins.c instead, could readily be extended to fold into
>> strings expressions like
>>
>>    sprintf (buf, "%i", 123);
>
> I've commented in some PR a few years ago that I'm not convinced we want to
> do it, or at least not without careful considerations, consider .rodata
> size.  Say if the user has in 1000x different places
> sprintf (buf, "foobarbaz %i", NNN); for various values of NNN, then such "optimization" would replace
> a single string literal of length 13 bytes with 1000 string literals of 12-20 bytes.
> Consider larger string literal, with %s and long additions and it might not
> be a win even for 2 occurrences.

I agree that's something to consider.  But even if the call itself
weren't folded, the return value (i.e., the number of characters
computed by the checker) could be.

Martin
Richard Biener July 5, 2016, 10:10 a.m. UTC | #6
On Mon, 4 Jul 2016, Martin Sebor wrote:

> On 07/04/2016 04:59 AM, Richard Biener wrote:
> > On Fri, 1 Jul 2016, Martin Sebor wrote:
> > 
> > > The attached patch enhances compile-time checking for buffer overflow
> > > and output truncation in non-trivial calls to the sprintf family of
> > > functions under a new option -Wformat-length=[12].  This initial
> > > patch handles printf directives with string, integer, and simple
> > > floating arguments but eventually I'd like to extend it all other
> > > functions and directives for which it makes sense.
> > > 
> > > I made some choices in the implementation that resulted in trade-offs
> > > in the quality of the diagnostics.  I would be grateful for comments
> > > and suggestions how to improve them.  Besides the list I include
> > > Jakub who already gave me some feedback (thanks), Joseph who as
> > > I understand has deep knowledge of the c-format.c code, and Richard
> > > for his input on the LTO concern below.
> > > 
> > > 1) Making use of -Wformat machinery in c-family/c-format.c.  This
> > >     seemed preferable to duplicating some of the same code elsewhere
> > >     (I initially started implementing it in expand_builtin in
> > >     builtins.c).  It makes the implementation readily extensible
> > >     to all the same formats as those already handled for -Wformat.
> > >     One drawback is that unlike in expand_builtin, calls to these
> > >     functions cannot readily be folded.  Another drawback pointed
> > 
> > folded?  You mean this -W option changes code generation?
> 
> No, it doesn't.  What I meant is that the same code, when added
> in builtins.c instead, could readily be extended to fold into
> strings expressions like
> 
>   sprintf (buf, "%i", 123);
> 
> > 
> > >     out by Jakub is that since the code is only available in the
> > >     C and C++ compilers, it apparently may not be available with
> > >     an LTO compiler (I don't completely understand this problem
> > >     but I mention it in the interest of full disclosure). In light
> > >     of the dependency in (2) below, I don't see a way to avoid it
> > >     (moving c-format.c to the middle end was suggested but seemed
> > >     like too much of a change to me).
> > 
> > Yes, lto1 is not linked with C_COMMON_OBJS (that could be changed
> > of course at the expense of dragging in some dead code).  Moving
> > all the format stuff to the middle-end (or separated better so
> > the overhead in lto1 is lower) would be possible as well.
> > 
> > That said, a langhook as you add it highlights the issue with LTO.
> 
> 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.

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?).

Richard.

> 
> FWIW, I would expect a good number of other warnings to benefit
> from optimization and having a general solution for this problem
> to be helpful.  I also suspect this isn't the first time this
> issue has come up.  I'm wondering what solutions have already
> been considered and with what pros and cons (naively, I would
> think that factoring the relevant code out of cc1 into a shared
> library that lto1 could load should work).
> 
> Martin
> 
>
Florian Weimer July 12, 2016, 8:31 a.m. UTC | #7
On 07/01/2016 08:15 PM, Martin Sebor wrote:
> diff --git a/gcc/passes.c b/gcc/passes.c
> index 0565cfa..008799c 100644
> --- a/gcc/passes.c
> +++ b/gcc/passes.c
> @@ -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);

This seems to be some leftover from debugging.

Florian
Florian Weimer July 12, 2016, 9:51 a.m. UTC | #8
On 07/01/2016 08:15 PM, Martin Sebor wrote:
> The attached patch enhances compile-time checking for buffer overflow
> and output truncation in non-trivial calls to the sprintf family of
> functions under a new option -Wformat-length=[12].  This initial
> patch handles printf directives with string, integer, and simple
> floating arguments but eventually I'd like to extend it all other
> functions and directives for which it makes sense.

I tried your patch with the following code, which is close to a 
real-world example:

#include <stdio.h>

void print (const char *);

void
format_1 (unsigned address)
{
   unsigned char a = address >> 24;
   unsigned char b = address >> 16;
   unsigned char c = address >> 8;
   unsigned char d = address;
   char buf[15];
   sprintf ("%u.%u.%u.%u", buf, a, b, c, d);
   print (buf);
}

void
format_2 (unsigned address)
{
   char buf[15];
   sprintf ("%u.%u.%u.%u", buf,
            address >> 24,
            (address >> 16) & 0xff,
            (address >> 8) & 0xff,
            address & 0xff);
   print (buf);
}

I didn't get a warning (with -O2 and -Wformat-length=1 or 
-Wformat-length=2).  If the warning is implemented in builtin folding, I 
guess this has to be expected because there is no range information, and 
warning for all %us similar to those in the example would produce too 
many false positives.

Florian
Jakub Jelinek July 12, 2016, 9:54 a.m. UTC | #9
On Tue, Jul 12, 2016 at 11:51:50AM +0200, Florian Weimer wrote:
> On 07/01/2016 08:15 PM, Martin Sebor wrote:
> >The attached patch enhances compile-time checking for buffer overflow
> >and output truncation in non-trivial calls to the sprintf family of
> >functions under a new option -Wformat-length=[12].  This initial
> >patch handles printf directives with string, integer, and simple
> >floating arguments but eventually I'd like to extend it all other
> >functions and directives for which it makes sense.
> 
> I tried your patch with the following code, which is close to a real-world
> example:
> 
> #include <stdio.h>
> 
> void print (const char *);
> 
> void
> format_1 (unsigned address)
> {
>   unsigned char a = address >> 24;
>   unsigned char b = address >> 16;
>   unsigned char c = address >> 8;
>   unsigned char d = address;
>   char buf[15];
>   sprintf ("%u.%u.%u.%u", buf, a, b, c, d);

Are you sure this is real-world code?  sprintf's first argument is the
buffer and second the format string, so if this doesn't warn at compile
time, it will surely crash at runtime when trying to store into .rodata.

	Jakub
Florian Weimer July 12, 2016, 10:01 a.m. UTC | #10
On 07/12/2016 11:54 AM, Jakub Jelinek wrote:
> On Tue, Jul 12, 2016 at 11:51:50AM +0200, Florian Weimer wrote:
>> On 07/01/2016 08:15 PM, Martin Sebor wrote:
>>> The attached patch enhances compile-time checking for buffer overflow
>>> and output truncation in non-trivial calls to the sprintf family of
>>> functions under a new option -Wformat-length=[12].  This initial
>>> patch handles printf directives with string, integer, and simple
>>> floating arguments but eventually I'd like to extend it all other
>>> functions and directives for which it makes sense.
>>
>> I tried your patch with the following code, which is close to a real-world
>> example:
>>
>> #include <stdio.h>
>>
>> void print (const char *);
>>
>> void
>> format_1 (unsigned address)
>> {
>>   unsigned char a = address >> 24;
>>   unsigned char b = address >> 16;
>>   unsigned char c = address >> 8;
>>   unsigned char d = address;
>>   char buf[15];
>>   sprintf ("%u.%u.%u.%u", buf, a, b, c, d);
>
> Are you sure this is real-world code?  sprintf's first argument is the
> buffer and second the format string, so if this doesn't warn at compile
> time, it will surely crash at runtime when trying to store into .rodata.

Argh!  You are right, I swapped the arguments.

And further attempts showed that I was missing -D_FORTIFY_SOURCE=2. 
With it, I get a nice diagnostic.  Wow!

Florian
Bernd Schmidt July 12, 2016, 12:32 p.m. UTC | #11
On 07/01/2016 08:15 PM, Martin Sebor wrote:
> The attached patch enhances compile-time checking for buffer overflow
> and output truncation in non-trivial calls to the sprintf family of
> functions under a new option -Wformat-length=[12].  This initial
> patch handles printf directives with string, integer, and simple
> floating arguments but eventually I'd like to extend it all other
> functions and directives for which it makes sense.

On the whole I think this looks good.

> +      /* Check the whole format strings and ist arguments for possible
> +	 buffer overflow or truncation.  */

"its"

>  struct function_format_info
>  {
> +  built_in_function fncode;
>    int format_type;			/* type of format (printf, scanf, etc.) */
>    unsigned HOST_WIDE_INT format_num;	/* number of format argument */
>    unsigned HOST_WIDE_INT first_arg_num;	/* number of first arg (zero for varargs) */
> +  /* The destination object size or -1 if unknown.  */
> +  unsigned HOST_WIDE_INT objsize;
> +
> +  /* True for functions whose output is bounded by a size argument
> +     (e.g., snprintf and vsnprintf).  */
> +  bool bounded;
>  };

While you're here could you fix the existing comments to be formatted 
like the ones you're adding (in front of the thing they're documenting, 
etc.)?

> +static void
> +check_format_length (location_t,
> +		     format_check_results *,
> +		     const function_format_info *,
> +		     const format_char_info *,
> +		     const char *, size_t, size_t,
> +		     const conversion_spec *, tree);

Only break the line before the function name for definitions, not 
declarations.

> +    unsigned char c = chr & 0xff;
> +    return flags [c / (CHAR_BIT * sizeof *flags)]
> +      & (1 << (c % (CHAR_BIT * sizeof *flags)));

 > +    unsigned char c = chr & 0xff;
 > +    flags [c / (CHAR_BIT * sizeof *flags)]
 > +      |= (1 << (c % (CHAR_BIT * sizeof *flags)));

Multi-line expressions should be wrapped in parentheses for correct 
formatting. Also, no space before [] brackets.

> +/* Return the logarithm of tree node X in BASE, incremented by 1 when
> +   the optional PLUS sign is True, plus the length of the octal ('0')
> +   or hexadecimal ('0x') prefix when PREFIX is True.  Return -1 when
> +   X cannot be represented.  */

Maybe this could be clearer as
/* Return the number of characters required to print X, which must be
    an INTEGER_CST, in BASE.  PLUS indicates whether a plus sign should
    be prepended to positive numbers, and PREFIX indicates whether an
    octal ('O') or hexadecimal ('0x') prefix should be prepended to
    nonzero numbesr.  Return -1 if X cannot be represented.  */

> +  if (prefix && absval)

This surprised me, but I checked and printf really does not seem to 
print 0x0.

> +/* 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.  */

Should indicate that ARG can be null, and what happens in that case. 
It's not entirely clear to me actually why it would be NULL sometimes.

> +    width = (TREE_CODE (spec->star_width) == INTEGER_CST)
> +            ? tree_to_shwi (spec->star_width) : 0;

> +    prec = (TREE_CODE (spec->star_precision) == INTEGER_CST)
> +           ? tree_to_shwi (spec->star_precision) : 0;

Wrapping. Lose parens around the condition, and wrap the whole expression.

> +  /* A 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;

"The type" maybe? Also, the structure of this function gets confusing 
around this point. It looks like we set argtype for everything except 
integer constants, then have a big block handling nonnull argtype and 
returning, then we have another block dealing with the other case. I 
think it would be clearer to check for INTEGER_CST first, deal with it, 
and then doing the type-based estimation.

> +  else if (TREE_CODE (arg) == INTEGER_CST)
> +    res.bounded = true;

The assignment appears to be repeated at the end of the function.

> +    width = (TREE_CODE (spec->star_width) == INTEGER_CST)
> +            ? tree_to_shwi (spec->star_width) : 0;

> +    prec = (TREE_CODE (spec->star_precision) == INTEGER_CST)
> +            ? tree_to_shwi (spec->star_precision) : 0;

See other instances. These lines occur several times in multiple functions.

> +  int expdigs = -1;    /* Number of exponent digits or -1 when unknown.  */
> +  int negative = -1;   /* 1 when arg < 0, 0 when arg >= 0, -1 when unknown.  */

I think we should avoid end-of-line comments. I tend to sometimes feel 
they are ok-ish in struct definitions, but let's not have them in code.

> +      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);

Wrapping.

> +      /* 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 = (spec->get_flag ('L') ? 4934 : 310) */
> +      /* 	+ (prec < 0 ? 6 : prec ? prec + 1 : 0); */
> +      res.max = expdigs + (prec < 0 ? 6 : prec ? prec + 1 : 0);

Not sure what the commented code is trying to tell me.

> +      res.max = (spec->get_flag ('L') ? 4934 : 310)
> +	+ (prec < 0 ? 6 : prec ? prec + 1 : 0);

Wrapping.

> +	  int nul = arg && TREE_CODE (arg) == INTEGER_CST
> +	    ? integer_zerop (arg) : -1;

Wrapping.

> +  else if (tree slen = arg ? c_strlen (arg, 1) : NULL_TREE)

Can c_strlen return NULL_TREE? If not I'd prefer to just test for arg in 
the if, and have the slen declaration moved inside it.

> +/* FIXME: Suppress the warning to avoid having to change all
> +   the format_char_info arrays below and to reduce the footprint
> +   of the patch until it's been reviewed and accepted.  */

Please make sure this is removed in the next iteration...

Isn't this easy to fix anyway, replace "NULL }" with "NULL, NULL }"?

> +#pragma GCC diagnostic pop

Also don't forget this one.

> +  /* Number of characters written by the formatting function, exact,
> +     minimum and maximum when an exact number cannot be determined.

I think this would be slightly better as "The number of characters 
written by the formatting function, either the exact number, or, if it 
cannot be determined, minimum and maximum."

> +     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.  */

That's also slightly unclear. (Setting either of the other two to what?) 
Also, what indicates that we have an exact value rather than min/max?

> +  /* True when the range given by NUMBER_CHARS_MIN and NUMBER_CHARS_MAX

"if" not "when" is preferrable I think; this occurs fairly consistently 
throughout the patch.

> +  /* Increment the number of output characters by N.  */
> +  void inc_number_chars (int n = 1) {

Not sure we have figured out how to format member functions but this 
isn't it. Two linebreaks missing I think.

>  void
> -check_function_format (tree attrs, int nargs, tree *argarray)
> +check_function_format (const_tree fndecl, const_tree attrs, int nargs, tree *argarray)

80-character limit.

> +  /* Avoid issuing one set of diagnostics during parsing, and another
> +     (possibly duplicate) set when expanding printf built-ins.  There
> +     are tradeoffs between the diagnostics issued during parsing and
> +     later: the former doesn't benefit from constant propagation or
> +     range information from the VRP pass, and the latter may not have
> +     the original types of all the arguments.  As a result, for
> +     arguments of narrower types with no range information that are
> +     promoted to wider types the diagnostics during parsing can reduce
> +     the rate of false positives by assuming the range of values of
> +     the original type as opposed to the one after promotion.
> +  */

Comment terminator goes at the end of the previous line.

> +  /* 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;

This is HOST_WIDE_INT_M1U. Occurs in a few other places too.

> +		  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 ();

Wrapping.

> +	      if (res->number_chars < 0)
> +		fmtstr = info->bounded
> +		  ? "output may be truncated at or before format character "
> +		    "%qc at offset %qlu past the end of a region of size %qlu"
> +		  : "writing format character %qc at offset %qlu "
> +		    "in a region of size %qlu";
> +	      else
> +		fmtstr = info->bounded
> +		  ? "output truncated at format character %qc at offset %qlu "
> +		    "just past the end of a region of size %qlu"
> +		  : "writing format character %qc at offset %qlu "
> +		    "just past the end of a region of size %qlu";

Wrapping.

> +     formt directives or arguments encountered during processing

"format"

> +     a possible overflow ("may write"), a certain overlow somewhere "past

"overflow"

+
> +      bool boundrange = res->number_chars_min < 0
> +	|| (res->number_chars_min < res->number_chars_max);

Wrapping. Also useless parens around the second condition.

> +      const char* fmtstr = info->bounded
> +	? (res->number_chars < 0 && boundrange
> +	   ? "output may be truncated while copying format string "
> +	     "into a region of size %qlu"
> +	   : "output truncated while copying format string "
> +	     "into a region of size %qlu")
> +	: (res->number_chars < 0 ? boundrange
> +	   ? "may write a terminating nul past the end "
> +	     "of a region of size %qlu"
> +	   : "writing a terminating nul past the end "
> +	     "of a region of size %qlu"
> +	   : "writing a terminating nul just past the end "
> +	     "of a region of size %qlu");

Wrapping.
>
> +static void
> +check_format_length (location_t                  loc,
> +		     format_check_results       *res,
> +		     const function_format_info *info,
> +		     const format_char_info     *fci,
> +		     const char                 *cvtbeg,
> +		     size_t                      cvtlen,
> +		     size_t                      offset,
> +		     const conversion_spec      *cvtspec,
> +		     tree                        arg)

Please don't format arguments like this.

> +	      const char* fmtstr = info->bounded
> +		? "%<%%%.*s%> directive output truncated %qi bytes "
> +		  "into a region of size %qlu"
> +		: "%<%%%.*s%> directive writing at least %qi bytes "
> +		  "into a region of size %qlu";
> +		warning_at (loc, OPT_Wformat_length_, fmtstr,
> +			    (int)cvtlen, cvtbeg, fmtres.min,
> +			    (unsigned long)navail);

> +	      const char* fmtstr = info->bounded
> +		? "%<%%%.*s%> directive output may be truncated between "
> +		  "%qi and %qi bytes into a region of size %qlu"
> +		: "%<%%%.*s%> directive writing between %qi and %qi bytes "
> +		  "into a region of size %qlu";

Wrapping.

> +	  if (info->bounded)
> +	    fmtstr = 1 < fmtres.min
> +	      ? "%<%%%.*s%> directive output truncated while writing "
> +	        "%qi bytes into a region of size %qlu"
> +	      : "%<%%%.*s%> directive output truncated while writing "
> +	        "%qi byte into a region of size %qlu";
> +	  else
> +	    fmtstr = 1 < fmtres.min
> +	      ? "%<%%%.*s%> directive writing %qi bytes "
> +	        "into a region of size %qlu"
> +	      : "%<%%%.*s%> directive writing %qi byte "
> +	        "into a region of size %qlu";

Here too.

> +  struct fmtresult {

Newline before brace?

> +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.

I think this ought to be "In most cases, enabling optimization improves 
the accuracy..."

> +NUL character

No idea whether NUL needs markup. Existing documentation is 
inconsistent, both for NUL and NULL.

> +buffer in an inforational note following the warning.

"informational"

> +arguments can be bounded by specifying the precision in the fortmat

"format"

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

"example"

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

Avoiding the warning? Real problem or false positive?

> +void
> +lhd_check_format_length (const_tree ARG_UNUSED (fndecl),
> +			 const_tree ARG_UNUSED (attrs),
> +			 int ARG_UNUSED (nargs),
> +			 tree * ARG_UNUSED (argarray))
> +{
> +}

I'm tempted to say let's just write C++, i.e. drop arg names altogether 
in obviously unused cases like this.

> +
> +      // 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);
> +	}

This ought to be removed.

> +  /* Ra creates a range of signed char for A [idx].  A different
> +     value is used each time to prevent the ranges from intesecting

"intersecting"

Beyond these I have no objections to the patch but ideally a C frontend 
maintainer would given an explicit ack as well.


Bernd
Martin Sebor July 12, 2016, 2:45 p.m. UTC | #12
On 07/12/2016 04:01 AM, Florian Weimer wrote:
> On 07/12/2016 11:54 AM, Jakub Jelinek wrote:
>> On Tue, Jul 12, 2016 at 11:51:50AM +0200, Florian Weimer wrote:
>>> On 07/01/2016 08:15 PM, Martin Sebor wrote:
>>>> The attached patch enhances compile-time checking for buffer overflow
>>>> and output truncation in non-trivial calls to the sprintf family of
>>>> functions under a new option -Wformat-length=[12].  This initial
>>>> patch handles printf directives with string, integer, and simple
>>>> floating arguments but eventually I'd like to extend it all other
>>>> functions and directives for which it makes sense.
>>>
>>> I tried your patch with the following code, which is close to a
>>> real-world
>>> example:
>>>
>>> #include <stdio.h>
>>>
>>> void print (const char *);
>>>
>>> void
>>> format_1 (unsigned address)
>>> {
>>>   unsigned char a = address >> 24;
>>>   unsigned char b = address >> 16;
>>>   unsigned char c = address >> 8;
>>>   unsigned char d = address;
>>>   char buf[15];
>>>   sprintf ("%u.%u.%u.%u", buf, a, b, c, d);
>>
>> Are you sure this is real-world code?  sprintf's first argument is the
>> buffer and second the format string, so if this doesn't warn at compile
>> time, it will surely crash at runtime when trying to store into .rodata.
>
> Argh!  You are right, I swapped the arguments.
>
> And further attempts showed that I was missing -D_FORTIFY_SOURCE=2. With
> it, I get a nice diagnostic.  Wow!

Thanks for giving it a try!  Based on the feedback I received
I've since updated the patch and will post the latest version
for review soon.  In simple cases like this one it warns even
without _FORTIFY_SOURCE or optimization (though without the
latter it doesn't benefit from VRP information).  Let me see
about adding a warning to detect and warn when the arguments
are transposed.

$ /build/gcc-49905/gcc/xgcc -B /build/gcc-49905/gcc -S -Wall -Wextra 
-Wpedantic -Wformat-length=2 xyz.c

xyz.c: In function ‘format_1’:
xyz.c:13:29: warning: may write a terminating nul past the end of the 
destination [-Wformat-length=]
    sprintf (buf, "%u.%u.%u.%u", a, b, c, d);
                              ^
xyz.c:13:3: note: destination size is ‘15’ bytes, output size between 
‘8’ and ‘16’
    sprintf (buf, "%u.%u.%u.%u", a, b, c, d);
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
xyz.c: In function ‘format_2’:
xyz.c:21:3: warning: ‘%u’ directive writing between ‘1’ and ‘10’ bytes 
into a region of size ‘4’ [-Wformat-length=]
    sprintf (buf, "%u.%u.%u.%u",
    ^
xyz.c:21:3: note: using the range [‘1u’, ‘2147483648u’] for directive 
argument
xyz.c:21:3: note: destination size is ‘15’ bytes, output size between 
‘4’ and ‘22’
    sprintf (buf, "%u.%u.%u.%u",
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
             address >> 24,
             ~~~~~~~~~~~~~~
             (address >> 16) & 0xff,
             ~~~~~~~~~~~~~~~~~~~~~~~
             (address >> 8) & 0xff,
             ~~~~~~~~~~~~~~~~~~~~~~
             address & 0xff);
             ~~~~~~~~~~~~~~~
xyz.c:21:3: note: destination size is ‘15’ bytes, output size between 
‘6’ and ‘33’

Martin
David Malcolm July 12, 2016, 3:01 p.m. UTC | #13
On Tue, 2016-07-12 at 08:45 -0600, Martin Sebor wrote:
> On 07/12/2016 04:01 AM, Florian Weimer wrote:
> > On 07/12/2016 11:54 AM, Jakub Jelinek wrote:
> > > On Tue, Jul 12, 2016 at 11:51:50AM +0200, Florian Weimer wrote:
> > > > On 07/01/2016 08:15 PM, Martin Sebor wrote:
> > > > > The attached patch enhances compile-time checking for buffer
> > > > > overflow
> > > > > and output truncation in non-trivial calls to the sprintf
> > > > > family of
> > > > > functions under a new option -Wformat-length=[12].  This
> > > > > initial
> > > > > patch handles printf directives with string, integer, and
> > > > > simple
> > > > > floating arguments but eventually I'd like to extend it all
> > > > > other
> > > > > functions and directives for which it makes sense.
> > > > 
> > > > I tried your patch with the following code, which is close to a
> > > > real-world
> > > > example:
> > > > 
> > > > #include <stdio.h>
> > > > 
> > > > void print (const char *);
> > > > 
> > > > void
> > > > format_1 (unsigned address)
> > > > {
> > > >   unsigned char a = address >> 24;
> > > >   unsigned char b = address >> 16;
> > > >   unsigned char c = address >> 8;
> > > >   unsigned char d = address;
> > > >   char buf[15];
> > > >   sprintf ("%u.%u.%u.%u", buf, a, b, c, d);
> > > 
> > > Are you sure this is real-world code?  sprintf's first argument
> > > is the
> > > buffer and second the format string, so if this doesn't warn at
> > > compile
> > > time, it will surely crash at runtime when trying to store into
> > > .rodata.
> > 
> > Argh!  You are right, I swapped the arguments.
> > 
> > And further attempts showed that I was missing -D_FORTIFY_SOURCE=2.
> > With
> > it, I get a nice diagnostic.  Wow!

Does it warn for the code that Florian actually posted?  I tried it
with a recent (unpatched) build of trunk and got no warning (with -O2 
-Wall -D_FORTIFY_SOURCE=2), but it strikes me that we ought to warn if
someone passes about the above code (for the uninitialized format
string, at least; I don't know if it's legal to pass a string literal
as the destination).

Should I file a PR for this?

> Thanks for giving it a try!  Based on the feedback I received
> I've since updated the patch and will post the latest version
> for review soon.  In simple cases like this one it warns even
> without _FORTIFY_SOURCE or optimization (though without the
> latter it doesn't benefit from VRP information).  Let me see
> about adding a warning to detect and warn when the arguments
> are transposed.
> 
> $ /build/gcc-49905/gcc/xgcc -B /build/gcc-49905/gcc -S -Wall -Wextra 
> -Wpedantic -Wformat-length=2 xyz.c
> 
> xyz.c: In function ‘format_1’:
> xyz.c:13:29: warning: may write a terminating nul past the end of the
> destination [-Wformat-length=]
>     sprintf (buf, "%u.%u.%u.%u", a, b, c, d);
>                               ^
> xyz.c:13:3: note: destination size is ‘15’ bytes, output size between
> ‘8’ and ‘16’
>     sprintf (buf, "%u.%u.%u.%u", a, b, c, d);
>     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Style question: should the numbers in these diagnostic messages be in
quotes?  That looks a bit strange to me.  It seems clearer to me to
have:

xyz.c:13:3: note: destination size is 15 bytes, output size between 8
and 16

> xyz.c: In function ‘format_2’:
> xyz.c:21:3: warning: ‘%u’ directive writing between ‘1’ and ‘10’
> bytes 
> into a region of size ‘4’ [-Wformat-length=]
>     sprintf (buf, "%u.%u.%u.%u",
>     ^
> xyz.c:21:3: note: using the range [‘1u’, ‘2147483648u’] for directive
> argument
> xyz.c:21:3: note: destination size is ‘15’ bytes, output size between
> ‘4’ and ‘22’
>     sprintf (buf, "%u.%u.%u.%u",
>     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
>              address >> 24,
>              ~~~~~~~~~~~~~~
>              (address >> 16) & 0xff,
>              ~~~~~~~~~~~~~~~~~~~~~~~
>              (address >> 8) & 0xff,
>              ~~~~~~~~~~~~~~~~~~~~~~
>              address & 0xff);
>              ~~~~~~~~~~~~~~~
> xyz.c:21:3: note: destination size is ‘15’ bytes, output size between
> ‘6’ and ‘33’
> 
> Martin
Martin Sebor July 12, 2016, 3:59 p.m. UTC | #14
> Does it warn for the code that Florian actually posted?  I tried it
> with a recent (unpatched) build of trunk and got no warning (with -O2
> -Wall -D_FORTIFY_SOURCE=2), but it strikes me that we ought to warn if
> someone passes about the above code (for the uninitialized format
> string, at least; I don't know if it's legal to pass a string literal
> as the destination).
>
> Should I file a PR for this?

It doesn't warn for it because in C, string literals are of type
char[] and so implicitly convertible to char* (shudder).  GCC
does warn for it with -Wwrite-strings or -Wdiscarded-qualifiers,
and will give a pedantic warning in C++ (it seems an error would
be preferable).

>
>> Thanks for giving it a try!  Based on the feedback I received
>> I've since updated the patch and will post the latest version
>> for review soon.  In simple cases like this one it warns even
>> without _FORTIFY_SOURCE or optimization (though without the
>> latter it doesn't benefit from VRP information).  Let me see
>> about adding a warning to detect and warn when the arguments
>> are transposed.
>>
>> $ /build/gcc-49905/gcc/xgcc -B /build/gcc-49905/gcc -S -Wall -Wextra
>> -Wpedantic -Wformat-length=2 xyz.c
>>
>> xyz.c: In function ‘format_1’:
>> xyz.c:13:29: warning: may write a terminating nul past the end of the
>> destination [-Wformat-length=]
>>      sprintf (buf, "%u.%u.%u.%u", a, b, c, d);
>>                                ^
>> xyz.c:13:3: note: destination size is ‘15’ bytes, output size between
>> ‘8’ and ‘16’
>>      sprintf (buf, "%u.%u.%u.%u", a, b, c, d);
>>      ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>
> Style question: should the numbers in these diagnostic messages be in
> quotes?  That looks a bit strange to me.  It seems clearer to me to
> have:

You're probably right.  I suspect I have a tendency to overuse
the quotes (e.g, the -Wplacement-new warning also quotes the
sizes).  If there aren't yet (I vague recall coming across
something on the GCC Wiki but can't find it now), it would be
helpful to put in place some diagnostic style conventions like
there are for formatting code to guide us in cases like this.
I'm willing to help put the document together or add this to
it if one already exists.

Martin
Manuel López-Ibáñez July 12, 2016, 4:12 p.m. UTC | #15
On 12/07/16 16:59, Martin Sebor wrote:
> You're probably right.  I suspect I have a tendency to overuse
> the quotes (e.g, the -Wplacement-new warning also quotes the
> sizes).  If there aren't yet (I vague recall coming across
> something on the GCC Wiki but can't find it now), it would be
> helpful to put in place some diagnostic style conventions like
> there are for formatting code to guide us in cases like this.
> I'm willing to help put the document together or add this to
> it if one already exists.

https://gcc.gnu.org/wiki/DiagnosticsGuidelines
Martin Sebor July 12, 2016, 9:43 p.m. UTC | #16
On 07/12/2016 06:32 AM, Bernd Schmidt wrote:
> On 07/01/2016 08:15 PM, Martin Sebor wrote:
>> The attached patch enhances compile-time checking for buffer overflow
>> and output truncation in non-trivial calls to the sprintf family of
>> functions under a new option -Wformat-length=[12].  This initial
>> patch handles printf directives with string, integer, and simple
>> floating arguments but eventually I'd like to extend it all other
>> functions and directives for which it makes sense.
>
> On the whole I think this looks good.

Thanks the detailed review!

> Beyond these I have no objections to the patch but ideally a C frontend
> maintainer would given an explicit ack as well.

In response to prior comments from Jakub and Richard I have actually
moved the patch to the middle end, into a pass of its own where it
works with LTO, and where it can also be used to optimize branches
based on the functions' return value (when they are known to be
exact).

I will make the changes you suggested (those that apply) and post
an updated patch for review soon that should be closer to final
than the initial prototype.

Martin
Martin Sebor July 12, 2016, 10:35 p.m. UTC | #17
On 07/12/2016 10:12 AM, Manuel López-Ibáñez wrote:
> On 12/07/16 16:59, Martin Sebor wrote:
>> You're probably right.  I suspect I have a tendency to overuse
>> the quotes (e.g, the -Wplacement-new warning also quotes the
>> sizes).  If there aren't yet (I vague recall coming across
>> something on the GCC Wiki but can't find it now), it would be
>> helpful to put in place some diagnostic style conventions like
>> there are for formatting code to guide us in cases like this.
>> I'm willing to help put the document together or add this to
>> it if one already exists.
>
> https://gcc.gnu.org/wiki/DiagnosticsGuidelines

That's it!  Thanks!  Looks like there are two places that talk
about GCC diagnostics: one on the Wiki and one in the Coding
Conventions (plus the GNU Coding Standard).  But, AFAICS, none
of these gives guidance for what to quote.

Based on the gcc.pot file it does look like quoted numbers are
far less common than unquoted ones (just 10 messages where they
are quoted vs 528 unquoted).

I've added this as a guideline to the Wiki and assuming no one
suggests otherwise I'll remove the quotes from this patch and
from the other changes I already committed.

Martin
Marek Polacek July 13, 2016, 8:18 a.m. UTC | #18
On Tue, Jul 12, 2016 at 04:35:50PM -0600, Martin Sebor wrote:
> Based on the gcc.pot file it does look like quoted numbers are
> far less common than unquoted ones (just 10 messages where they
> are quoted vs 528 unquoted).
> 
> I've added this as a guideline to the Wiki and assuming no one
> suggests otherwise I'll remove the quotes from this patch and
> from the other changes I already committed.

I will support the change as I don't see any need for quoting numbers,
as opposed to e.g. identifiers.

	Marek
David Malcolm July 13, 2016, 9:08 a.m. UTC | #19
On Tue, 2016-07-12 at 16:35 -0600, Martin Sebor wrote:
> On 07/12/2016 10:12 AM, Manuel López-Ibáñez wrote:
> > On 12/07/16 16:59, Martin Sebor wrote:
> > > You're probably right.  I suspect I have a tendency to overuse
> > > the quotes (e.g, the -Wplacement-new warning also quotes the
> > > sizes).  If there aren't yet (I vague recall coming across
> > > something on the GCC Wiki but can't find it now), it would be
> > > helpful to put in place some diagnostic style conventions like
> > > there are for formatting code to guide us in cases like this.
> > > I'm willing to help put the document together or add this to
> > > it if one already exists.
> > 
> > https://gcc.gnu.org/wiki/DiagnosticsGuidelines
> 
> That's it!  Thanks!  Looks like there are two places that talk
> about GCC diagnostics: one on the Wiki and one in the Coding
> Conventions (plus the GNU Coding Standard).  But, AFAICS, none
> of these gives guidance for what to quote.
> 
> Based on the gcc.pot file it does look like quoted numbers are
> far less common than unquoted ones (just 10 messages where they
> are quoted vs 528 unquoted).
> 
> I've added this as a guideline to the Wiki and assuming no one
> suggests otherwise I'll remove the quotes from this patch and
> from the other changes I already committed.

For reference, Martin's wiki change was:
https://gcc.gnu.org/wiki/DiagnosticsGuidelines?action=diff&rev2=8&rev1=7

(looks good to me, fwiw)
Manuel López-Ibáñez July 14, 2016, 1:57 a.m. UTC | #20
On 01/07/16 19:15, Martin Sebor wrote:
+	      /* Differentiate between an exact and inexact buffer overflow
+		 or truncation.  */
+	      const char *fmtstr;
+	      if (res->number_chars < 0)
+		fmtstr = info->bounded
+		  ? "output may be truncated at or before format character "
+		    "%qc at offset %qlu past the end of a region of size %qlu"
+		  : "writing format character %qc at offset %qlu "
+		    "in a region of size %qlu";
+	      else
+		fmtstr = info->bounded
+		  ? "output truncated at format character %qc at offset %qlu "
+		    "just past the end of a region of size %qlu"
+		  : "writing format character %qc at offset %qlu "
+		    "just past the end of a region of size %qlu";
+	      warning_at (loc, OPT_Wformat_length_, fmtstr,
+			  format_chars [-1], off - 1,
+			  (unsigned long)info->objsize);
+	    }


I'm not sure gettext can parse the text of format strings given like this. It 
may be smarter enough if the conditional expression is directly the argument to 
warning_at. GCC's -Wformat has the same limitations. Of course, the fool-proof 
way is to use multiple calls to warning_at.

Cheers,
	Manuel.
diff mbox

Patch

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

gcc/c/ChangeLog:
2016-07-01  Martin Sebor  <msebor@redhat.com>

	PR middle-end/49905
	* c-lang.c (LANG_HOOKS_CHECK_FORMAT_LENGTH): Define.
	* c-typeck.c (check_function_arguments): Add an argument.

gcc/c-family/ChangeLog:
2016-07-01  Martin Sebor  <msebor@redhat.com>

	PR middle-end/49905
	* c-common.c (check_function_arguments): Add an argument and pass
	it to check_function_format.
	* c-common.h (check_function_arguments): Add an argument.
	* c-format.c (function_format_info): Add new members.
	(conversion_spec): New struct.
	(check_format_length, ilog, tree_ilog, format_integer, format_floating,
	format_string, bytes_remaining): New functions.
	(print_char_table): Add initializers.
	(format_check_results): Add members.
	(check_function_format): Add an argument.
	(check_format_info): Add an argument.
	(check_format_info_main): Track the length of the formatted output
	and diagnose buffer overflow and truncation.
	(check_format_length): Handle -Wformat-length.
	(format_type_warning): Avoid bogus warnings when called during
	expansion.
	* c-format.h (format_char_info): Add members.
	* c.opt (-Wformat-legth=): Add new option.

gcc/cp/ChangeLog:
2016-07-01  Martin Sebor  <msebor@redhat.com>

	PR middle-end/49905
	* call.c (build_over_call): Pass function decl to
	check_function_arguments.
	* typeck.c (cp_build_function_call_vec): Same.
	* cp/cp-lang.c: Define LANG_HOOKS_CHECK_FORMAT_LENGTH.

gcc/testsuite/ChangeLog:
2016-07-01  Martin Sebor  <msebor@redhat.com>

	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:
2016-07-01  Martin Sebor  <msebor@redhat.com>

	PR middle-end/49905
	* builtins.c (expand_builtin): Call maybe_emit_snprintf_trunc_warning.
	Avoid issuing a duplicate warning already issued by -Wformat-length.
	(maybe_emit_sprintf_chk_warning): Call maybe_emit_snprintf_trunc_warning.
	(maybe_emit_snprintf_trunc_warning): New function.
	* langhooks-def.h (lhd_check_format_length): Declare new function.
	(LANG_HOOKS_CHECK_FORMAT_LENGTH): New macro.
	* langhooks.c (lhd_warn_unused_global_decl): Define it.
	* langhooks.h (struct lang_hooks_for_decls): Add check_format_length.
	* doc/invoke.texi (-Wformat-length=): Document new option.

diff --git a/gcc/builtins.c b/gcc/builtins.c
index 5d234a5..657c607 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -175,6 +175,7 @@  static rtx expand_builtin_memory_chk (tree, rtx, machine_mode,
 				      enum built_in_function);
 static void maybe_emit_chk_warning (tree, enum built_in_function);
 static void maybe_emit_sprintf_chk_warning (tree, enum built_in_function);
+static void maybe_emit_snprintf_trunc_warning (tree);
 static void maybe_emit_free_warning (tree);
 static tree fold_builtin_object_size (tree, tree);
 
@@ -6688,11 +6689,17 @@  expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode,
     case BUILT_IN_STPNCPY_CHK:
     case BUILT_IN_STRCAT_CHK:
     case BUILT_IN_STRNCAT_CHK:
+
     case BUILT_IN_SNPRINTF_CHK:
     case BUILT_IN_VSNPRINTF_CHK:
       maybe_emit_chk_warning (exp, fcode);
       break;
 
+    case BUILT_IN_SNPRINTF:
+    case BUILT_IN_VSNPRINTF:
+      maybe_emit_snprintf_trunc_warning (exp);
+      break;
+
     case BUILT_IN_SPRINTF_CHK:
     case BUILT_IN_VSPRINTF_CHK:
       maybe_emit_sprintf_chk_warning (exp, fcode);
@@ -9346,6 +9353,12 @@  maybe_emit_chk_warning (tree exp, enum built_in_function fcode)
       break;
     case BUILT_IN_SNPRINTF_CHK:
     case BUILT_IN_VSNPRINTF_CHK:
+      /* Diagnose output truncation with -Wformat-length.  */
+      maybe_emit_snprintf_trunc_warning (exp);
+      /* -Wformat-length also diagnoses buffer size in excess of
+	 the object size so avoid diagnosing it again below.  */
+      if (warn_format_length)
+	return;
       len = CALL_EXPR_ARG (exp, 1);
       size = CALL_EXPR_ARG (exp, 3);
       break;
@@ -9387,34 +9400,52 @@  maybe_emit_chk_warning (tree exp, enum built_in_function fcode)
 	      exp, get_callee_fndecl (exp));
 }
 
+/* Emit warning if output truncation is detected at compile time
+   in __snprintf/__vsnprintf calls.  */
+
+static void
+maybe_emit_snprintf_trunc_warning (tree exp)
+{
+  /* Verify the required arguments in the original call.  */
+  int nargs = call_expr_nargs (exp);
+
+  if (nargs < 3)
+    return;
+
+  tree fundecl = TREE_OPERAND (CALL_EXPR_FN (exp), 0);
+  tree attrs = TYPE_ATTRIBUTES (TREE_TYPE (fundecl));
+  tree *params = &CALL_EXPR_STATIC_CHAIN (exp) + 1;
+  lang_hooks.decls.check_format_length (fundecl, attrs, nargs, params);
+}
+
 /* Emit warning if a buffer overflow is detected at compile time
    in __sprintf_chk/__vsprintf_chk calls.  */
 
 static void
 maybe_emit_sprintf_chk_warning (tree exp, enum built_in_function fcode)
 {
-  tree size, len, fmt;
-  const char *fmt_str;
   int nargs = call_expr_nargs (exp);
 
   /* Verify the required arguments in the original call.  */
 
   if (nargs < 4)
     return;
-  size = CALL_EXPR_ARG (exp, 2);
-  fmt = CALL_EXPR_ARG (exp, 3);
 
+  tree size = CALL_EXPR_ARG (exp, 2);
   if (! tree_fits_uhwi_p (size) || integer_all_onesp (size))
     return;
 
   /* Check whether the format is a literal string constant.  */
-  fmt_str = c_getstr (fmt);
+  tree fmt = CALL_EXPR_ARG (exp, 3);
+  const char *fmt_str = c_getstr (fmt);
   if (fmt_str == NULL)
     return;
 
   if (!init_target_chars ())
     return;
 
+  tree len = NULL_TREE;
+
   /* If the format doesn't contain % args or %%, we know its size.  */
   if (strchr (fmt_str, target_percent) == 0)
     len = build_int_cstu (size_type_node, strlen (fmt_str));
@@ -9423,25 +9454,32 @@  maybe_emit_sprintf_chk_warning (tree exp, enum built_in_function fcode)
   else if (fcode == BUILT_IN_SPRINTF_CHK
 	   && strcmp (fmt_str, target_percent_s) == 0)
     {
-      tree arg;
-
       if (nargs < 5)
 	return;
-      arg = CALL_EXPR_ARG (exp, 4);
+      tree arg = CALL_EXPR_ARG (exp, 4);
       if (! POINTER_TYPE_P (TREE_TYPE (arg)))
 	return;
 
       len = c_strlen (arg, 1);
-      if (!len || ! tree_fits_uhwi_p (len))
-	return;
     }
-  else
-    return;
 
-  if (! tree_int_cst_lt (len, size))
-    warning_at (tree_nonartificial_location (exp),
-		0, "%Kcall to %D will always overflow destination buffer",
-		exp, get_callee_fndecl (exp));
+  if (!len || !tree_fits_uhwi_p (len))
+    {
+      /* Check the whole format strings and ist arguments for possible
+	 buffer overflow or truncation.  */
+      tree fundecl = TREE_OPERAND (CALL_EXPR_FN (exp), 0);
+      tree attrs = TYPE_ATTRIBUTES (TREE_TYPE (fundecl));
+      tree *params = &CALL_EXPR_STATIC_CHAIN (exp) + 1;
+      lang_hooks.decls.check_format_length (fundecl, attrs, nargs, params);
+      return;
+    }
+  else if (!tree_int_cst_lt (len, size))
+    {
+      len = fold_build2 (PLUS_EXPR, size_type_node, len, integer_one_node);
+      warning_at (tree_nonartificial_location (exp),
+		  0, "%K %D writing %qE bytes into an object of size %qE",
+		  exp, get_callee_fndecl (exp), len, size);
+    }
 }
 
 /* Emit warning if a free is called with address of a variable.  */
diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c
index 85f3a03..524fbc5 100644
--- a/gcc/c-family/c-common.c
+++ b/gcc/c-family/c-common.c
@@ -9718,7 +9718,8 @@  handle_designated_init_attribute (tree *node, tree name, tree, int,
    There are NARGS arguments in the array ARGARRAY.  LOC should be used for
    diagnostics.  */
 void
-check_function_arguments (location_t loc, const_tree fntype, int nargs,
+check_function_arguments (location_t loc, const_tree fndecl,
+			  const_tree fntype, int nargs,
 			  tree *argarray)
 {
   /* Check for null being passed in a pointer argument that must be
@@ -9730,7 +9731,7 @@  check_function_arguments (location_t loc, const_tree fntype, int nargs,
   /* Check for errors in format strings.  */
 
   if (warn_format || warn_suggest_attribute_format)
-    check_function_format (TYPE_ATTRIBUTES (fntype), nargs, argarray);
+    check_function_format (fndecl, TYPE_ATTRIBUTES (fntype), nargs, argarray);
 
   if (warn_format)
     check_function_sentinel (fntype, nargs, argarray);
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index 4e6aa00..9f6012c 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -782,7 +782,8 @@  extern const char *fname_as_string (int);
 extern tree fname_decl (location_t, unsigned, tree);
 
 extern int check_user_alignment (const_tree, bool);
-extern void check_function_arguments (location_t loc, const_tree, int, tree *);
+extern void check_function_arguments (location_t loc, const_tree, const_tree,
+				      int, tree *);
 extern void check_function_arguments_recurse (void (*)
 					      (void *, tree,
 					       unsigned HOST_WIDE_INT),
@@ -790,7 +791,7 @@  extern void check_function_arguments_recurse (void (*)
 					      unsigned HOST_WIDE_INT);
 extern bool check_builtin_function_arguments (location_t, vec<location_t>,
 					      tree, int, tree *);
-extern void check_function_format (tree, int, tree *);
+extern void check_function_format (const_tree, const_tree, int, tree *);
 extern tree handle_unused_attribute (tree *, tree, tree, int, bool *);
 extern tree handle_format_attribute (tree *, tree, tree, int, bool *);
 extern tree handle_format_arg_attribute (tree *, tree, tree, int, bool *);
diff --git a/gcc/c-family/c-format.c b/gcc/c-family/c-format.c
index c19c411..78eff28 100644
--- a/gcc/c-family/c-format.c
+++ b/gcc/c-family/c-format.c
@@ -29,6 +29,12 @@  along with GCC; see the file COPYING3.  If not see
 #include "intl.h"
 #include "langhooks.h"
 #include "c-format.h"
+#include "builtins.h"
+
+#include "backend.h"
+#include "gimple.h"
+#include "ssa.h"
+#include "stor-layout.h"
 
 /* Handle attributes associated with format checking.  */
 
@@ -44,9 +50,16 @@  enum format_type { printf_format_type, asm_fprintf_format_type,
 
 struct function_format_info
 {
+  built_in_function fncode;
   int format_type;			/* type of format (printf, scanf, etc.) */
   unsigned HOST_WIDE_INT format_num;	/* number of format argument */
   unsigned HOST_WIDE_INT first_arg_num;	/* number of first arg (zero for varargs) */
+  /* The destination object size or -1 if unknown.  */
+  unsigned HOST_WIDE_INT objsize;
+
+  /* True for functions whose output is bounded by a size argument
+     (e.g., snprintf and vsnprintf).  */
+  bool bounded;
 };
 
 static bool decode_format_attr (tree, function_format_info *, int);
@@ -65,6 +78,16 @@  static int first_target_format_type;
 static const char *format_name (int format_num);
 static int format_flags (int format_num);
 
+struct format_check_results;
+
+static void
+check_format_length (location_t,
+		     format_check_results *,
+		     const function_format_info *,
+		     const format_char_info *,
+		     const char *, size_t, size_t,
+		     const conversion_spec *, tree);
+
 /* Given a string S of length LINE_WIDTH, find the visual column
    corresponding to OFFSET bytes.   */
 
@@ -378,6 +401,542 @@  decode_format_attr (tree args, function_format_info *info, int validated_p)
 
   return true;
 }
+
+/* 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.  */
+
+  /* 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 logarithm of tree node X in BASE, incremented by 1 when
+   the optional PLUS sign is True, plus the length of the octal ('0')
+   or hexadecimal ('0x') prefix when PREFIX is True.  Return -1 when
+   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;
+}
+
+/* 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.  */
+
+static format_char_info::fmtresult
+format_integer (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 : 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;
+
+  /* The minimum and maximum number of bytes produced by the directive.  */
+  format_char_info::fmtresult res;
+
+  bool sign = spec->specifier == 'd' || spec->specifier == 'i';
+  tree type = NULL_TREE;
+
+  switch (spec->modifier)
+    {
+    case FMT_LEN_none:
+      type = sign ? integer_type_node : unsigned_type_node;
+      break;
+
+    case FMT_LEN_h:
+      type = sign ? short_integer_type_node : short_unsigned_type_node;
+      break;
+
+    case FMT_LEN_hh:
+      type = sign ? signed_char_type_node : unsigned_char_type_node;
+      break;
+
+    case FMT_LEN_l:
+      type = sign ? long_integer_type_node : long_unsigned_type_node;
+      break;
+
+    case FMT_LEN_ll:
+      type = sign ? long_integer_type_node : long_unsigned_type_node;
+      break;
+
+    case FMT_LEN_z:
+      type = sign ? ptrdiff_type_node : size_type_node;
+      break;
+
+    case FMT_LEN_t:
+      type = sign ? ptrdiff_type_node : size_type_node;
+      break;
+
+    case FMT_LEN_j:
+      type = sign ? ptrdiff_type_node : size_type_node;
+      break;
+
+    case FMT_LEN_H:
+    case FMT_LEN_D:
+    case FMT_LEN_DD:
+    case FMT_LEN_MAX:
+      /* FIXME: Implement this. */
+      res.min = res.max = -1;
+      return res;
+
+    default:
+      gcc_unreachable ();
+    }
+
+  /* A 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;
+
+  /* The argument is likely unbounded (i.e., the range of its values
+     beyond the limits of its type is likely unknown.  */
+  res.bounded = false;
+
+  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 = type;
+    }
+  else if (TREE_CODE (arg) == INTEGER_CST)
+    res.bounded = true;
+  else
+    {
+      /* 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);
+    }
+
+  if (argtype)
+    {
+      tree argmin = NULL_TREE;
+      tree argmax = NULL_TREE;
+
+      if (arg && TREE_CODE (arg) == SSA_NAME)
+	{
+	  /* Try to determine the range of values of the argument.  */
+	  wide_int min, max;
+	  enum value_range_type range_type = get_range_info (arg, &min, &max);
+	  if (range_type == VR_RANGE)
+	    {
+	      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 ());
+
+	      /* The argument is bounded by the range of values
+		 determined by VRP.  */
+	      res.bounded = true;
+	    }
+	  else if (range_type == VR_ANTI_RANGE)
+	    {
+	      /* Handle anti-ranges if/when bug 71690 is ever resolved.  */
+	    }
+	}
+
+      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 (type);
+	  int argprec = TYPE_PRECISION (argtype);
+
+	  if (argprec < typeprec)
+	    {
+	      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, type, integer_one_node,
+				    build_int_cst (integer_type_node,
+						   typeprec - 1));
+	    }
+	}
+
+      /* 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;
+    }
+
+  /* Base to format the number in.  */
+  int base = 10;
+  /* True when a signed conversion is preceded by a sign or space.  */
+  bool maybesign = 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 'X':
+    case 'x':
+      base = 16;
+      break;
+    default:
+      gcc_unreachable ();
+    }
+
+  /* Convert the argument to the type of the directive.  */
+  arg = fold_convert (type, arg);
+
+  maybesign |= spec->get_flag ('+');
+  int len = tree_digits (arg, base, maybesign, spec->get_flag ('#'));
+
+  if (len < prec)
+    len = prec;
+
+  if (len < width)
+    len = width;
+
+  res.max = len;
+  res.min = res.max;
+  res.bounded = true;
+
+  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 format_char_info::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:
+      if (!type)
+	type = long_double_type_node;
+      break;
+
+    default:
+      gcc_unreachable ();
+    }
+
+  /* The minimum and maximum number of bytes produced by the directive.  */
+  format_char_info::fmtresult res;
+
+  int expdigs = -1;    /* Number of exponent digits or -1 when unknown.  */
+  int negative = -1;   /* 1 when arg < 0, 0 when arg >= 0, -1 when unknown.  */
+
+  if (arg && TREE_CODE (arg) == REAL_CST)
+    {
+      expdigs = ilog (real_exponent (TREE_REAL_CST_PTR (arg)), 10);
+      negative = real_isneg (TREE_REAL_CST_PTR (arg));
+    }
+  else if (REAL_MODE_FORMAT (TYPE_MODE (type))->b == 2)
+    {
+      /* Compute T_MAX_EXP for base 2.  */
+      const double log10_2 = .30102999566398119521;
+      expdigs = REAL_MODE_FORMAT (TYPE_MODE (type))->emax * log10_2;
+    }
+
+  int logexpdigs = ilog (expdigs, 10);
+
+  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 = (spec->get_flag ('L') ? 4934 : 310) */
+      /* 	+ (prec < 0 ? 6 : prec ? prec + 1 : 0); */
+      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 the minimum and maximum number of characters formatted
+   by the '%c' and '%s' format directives and ther wide character
+   forms.  */
+
+static format_char_info::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;
+
+  format_char_info::fmtresult res;
+
+  /* The argument is likely unbounded (i.e., its length is likely
+     unknown.  */
+  res.bounded = 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;
+    }
+  else if (tree slen = arg ? c_strlen (arg, 1) : NULL_TREE)
+    {
+      /* A '%s' directive with a constant string.  */
+      nbytes = tree_to_shwi (slen);
+      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;
+	  /* FIXME: Be smarter about computing the maximum.  Scan
+	     the wide string for any 8-bit characters and if it
+	     contains none, use its length for the maximum.  */
+	  res.max = 0 <= prec ? prec : -1;
+
+	  res.bounded = -1 < res.max;
+	  return res;
+	}
+    }
+  else
+    {
+      /* For a '%s' and '%ls' directive with a non-constant string,
+	 the minimum number of characters is the greater of WIDTH
+	 and either 0 in mode 1 or the smaller of PRECISION and 1
+	 in mode 2, and the maximum is PRECISION or -1 to disable
+	 tracking.  */
+      res.min = 0 < width ? width : 1 < warn_format_length && prec ? 1 : 0;
+      res.max = 0 <= prec ? prec : -1;
+      res.bounded = res.min == res.max;
+      return res;
+    }
+
+  if (nbytes < width)
+    nbytes = width;
+
+  res.min = res.max = nbytes;
+
+  /* The length is exact.  */
+  res.bounded = true;
+
+  return res;
+}
 
 /* Check a call to a format function against a parameter list.  */
 
@@ -670,21 +1229,27 @@  static const format_flag_pair strfmon_flag_pairs[] =
 };
 
 
+/* FIXME: Suppress the warning to avoid having to change all
+   the format_char_info arrays below and to reduce the footprint
+   of the patch until it's been reviewed and accepted.  */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
+
 static const format_char_info print_char_table[] =
 {
   /* C89 conversion specifiers.  */
-  { "di",  0, STD_C89, { T89_I,   T99_SC,  T89_S,   T89_L,   T9L_LL,  TEX_LL,  T99_SST, T99_PD,  T99_IM,  BADLEN,  BADLEN,  BADLEN  }, "-wp0 +'I",  "i",  NULL },
-  { "oxX", 0, STD_C89, { T89_UI,  T99_UC,  T89_US,  T89_UL,  T9L_ULL, TEX_ULL, T99_ST,  T99_UPD, T99_UIM, BADLEN,  BADLEN,  BADLEN }, "-wp0#",     "i",  NULL },
-  { "u",   0, STD_C89, { T89_UI,  T99_UC,  T89_US,  T89_UL,  T9L_ULL, TEX_ULL, T99_ST,  T99_UPD, T99_UIM, BADLEN,  BADLEN,  BADLEN }, "-wp0'I",    "i",  NULL },
-  { "fgG", 0, STD_C89, { T89_D,   BADLEN,  BADLEN,  T99_D,   BADLEN,  T89_LD,  BADLEN,  BADLEN,  BADLEN,  TEX_D32, TEX_D64, TEX_D128 }, "-wp0 +#'I", "",   NULL },
-  { "eE",  0, STD_C89, { T89_D,   BADLEN,  BADLEN,  T99_D,   BADLEN,  T89_LD,  BADLEN,  BADLEN,  BADLEN,  TEX_D32, TEX_D64, TEX_D128 }, "-wp0 +#I",  "",   NULL },
-  { "c",   0, STD_C89, { T89_I,   BADLEN,  BADLEN,  T94_WI,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN }, "-w",        "",   NULL },
-  { "s",   1, STD_C89, { T89_C,   BADLEN,  BADLEN,  T94_W,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN }, "-wp",       "cR", NULL },
+  { "di",  0, STD_C89, { T89_I,   T99_SC,  T89_S,   T89_L,   T9L_LL,  TEX_LL,  T99_SST, T99_PD,  T99_IM,  BADLEN,  BADLEN,  BADLEN  }, "-wp0 +'I",  "i",  NULL, format_integer },
+  { "oxX", 0, STD_C89, { T89_UI,  T99_UC,  T89_US,  T89_UL,  T9L_ULL, TEX_ULL, T99_ST,  T99_UPD, T99_UIM, BADLEN,  BADLEN,  BADLEN }, "-wp0#",     "i",  NULL, format_integer },
+  { "u",   0, STD_C89, { T89_UI,  T99_UC,  T89_US,  T89_UL,  T9L_ULL, TEX_ULL, T99_ST,  T99_UPD, T99_UIM, BADLEN,  BADLEN,  BADLEN }, "-wp0'I",    "i",  NULL, format_integer},
+  { "fgG", 0, STD_C89, { T89_D,   BADLEN,  BADLEN,  T99_D,   BADLEN,  T89_LD,  BADLEN,  BADLEN,  BADLEN,  TEX_D32, TEX_D64, TEX_D128 }, "-wp0 +#'I", "",   NULL, format_floating },
+  { "eE",  0, STD_C89, { T89_D,   BADLEN,  BADLEN,  T99_D,   BADLEN,  T89_LD,  BADLEN,  BADLEN,  BADLEN,  TEX_D32, TEX_D64, TEX_D128 }, "-wp0 +#I",  "",   NULL, format_floating },
+  { "c",   0, STD_C89, { T89_I,   BADLEN,  BADLEN,  T94_WI,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN }, "-w",        "",   NULL, format_string },
+  { "s",   1, STD_C89, { T89_C,   BADLEN,  BADLEN,  T94_W,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN }, "-wp",       "cR", NULL, format_string },
   { "p",   1, STD_C89, { T89_V,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN }, "-w",        "c",  NULL },
   { "n",   1, STD_C89, { T89_I,   T99_SC,  T89_S,   T89_L,   T9L_LL,  BADLEN,  T99_SST, T99_PD,  T99_IM,  BADLEN,  BADLEN,  BADLEN }, "",          "W",  NULL },
   /* C99 conversion specifiers.  */
-  { "F",   0, STD_C99, { T99_D,   BADLEN,  BADLEN,  T99_D,   BADLEN,  T99_LD,  BADLEN,  BADLEN,  BADLEN,  TEX_D32, TEX_D64, TEX_D128 }, "-wp0 +#'I", "",   NULL },
-  { "aA",  0, STD_C99, { T99_D,   BADLEN,  BADLEN,  T99_D,   BADLEN,  T99_LD,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN }, "-wp0 +#",   "",   NULL },
+  { "F",   0, STD_C99, { T99_D,   BADLEN,  BADLEN,  T99_D,   BADLEN,  T99_LD,  BADLEN,  BADLEN,  BADLEN,  TEX_D32, TEX_D64, TEX_D128 }, "-wp0 +#'I", "",   NULL, format_floating },
+  { "aA",  0, STD_C99, { T99_D,   BADLEN,  BADLEN,  T99_D,   BADLEN,  T99_LD,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN }, "-wp0 +#",   "",   NULL, format_floating },
   /* X/Open conversion specifiers.  */
   { "C",   0, STD_EXT, { TEX_WI,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN }, "-w",        "",   NULL },
   { "S",   1, STD_EXT, { TEX_W,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN }, "-wp",       "R",  NULL },
@@ -880,6 +1445,8 @@  static const format_char_info monetary_char_table[] =
   { NULL, 0, STD_C89, NOLENGTHS, NULL, NULL, NULL }
 };
 
+#pragma GCC diagnostic pop
+
 /* This must be in the same order as enum format_type.  */
 static const format_kind_info format_types_orig[] =
 {
@@ -984,8 +1551,35 @@  struct format_check_results
   int number_unterminated;
   /* Number of leaves of the format argument that were not counted above.  */
   int number_other;
+  /* 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;
+
   /* 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;
+  }
 };
 
 struct format_check_context
@@ -1015,7 +1609,7 @@  format_flags (int format_num)
   gcc_unreachable ();
 }
 
-static void check_format_info (function_format_info *, tree);
+static void check_format_info (function_format_info *, const_tree, tree);
 static void check_format_arg (void *, tree, unsigned HOST_WIDE_INT);
 static void check_format_info_main (format_check_results *,
 				    function_format_info *,
@@ -1069,17 +1663,15 @@  decode_format_type (const char *s)
    attribute themselves.  */
 
 void
-check_function_format (tree attrs, int nargs, tree *argarray)
+check_function_format (const_tree fndecl, const_tree attrs, int nargs, tree *argarray)
 {
-  tree a;
-
   /* See if this function has any format attributes.  */
-  for (a = attrs; a; a = TREE_CHAIN (a))
+  for (const_tree a = attrs; a; a = TREE_CHAIN (a))
     {
       if (is_attribute_p ("format", TREE_PURPOSE (a)))
 	{
 	  /* Yup; check it.  */
-	  function_format_info info;
+	  function_format_info info = function_format_info ();
 	  decode_format_attr (TREE_VALUE (a), &info, /*validated=*/true);
 	  if (warn_format)
 	    {
@@ -1090,7 +1682,7 @@  check_function_format (tree attrs, int nargs, tree *argarray)
 	      int i;
 	      for (i = nargs - 1; i >= 0; i--)
 		params = tree_cons (NULL_TREE, argarray[i], params);
-	      check_format_info (&info, params);
+	      check_format_info (&info, fndecl, params);
 	    }
 	  if (warn_suggest_attribute_format && info.first_arg_num == 0
 	      && (format_types[info.format_type].flags
@@ -1378,26 +1970,155 @@  get_flag_spec (const format_flag_spec *spec, int flag, const char *predicates)
   return NULL;
 }
 
-
 /* Check the argument list of a call to printf, scanf, etc.
    INFO points to the function_format_info structure.
    PARAMS is the list of argument values.  */
 
 static void
-check_format_info (function_format_info *info, tree params)
+check_format_info (function_format_info *info, const_tree fndecl, tree params)
 {
+  /* Avoid issuing one set of diagnostics during parsing, and another
+     (possibly duplicate) set when expanding printf built-ins.  There
+     are tradeoffs between the diagnostics issued during parsing and
+     later: the former doesn't benefit from constant propagation or
+     range information from the VRP pass, and the latter may not have
+     the original types of all the arguments.  As a result, for
+     arguments of narrower types with no range information that are
+     promoted to wider types the diagnostics during parsing can reduce
+     the rate of false positives by assuming the range of values of
+     the original type as opposed to the one after promotion.
+  */
+  const struct restore_on_return
+  {
+    int warn_format_save;
+    int warn_format_length_save;
+
+    restore_on_return ()
+    : warn_format_save (warn_format),
+      warn_format_length_save (warn_format_length)
+    {
+      if (!optimize ^ !currently_expanding_gimple_stmt)
+	warn_format_length = 0;
+      if (currently_expanding_gimple_stmt)
+	warn_format = 0;
+    }
+
+    ~restore_on_return () {
+      warn_format = warn_format_save;
+      warn_format_length = warn_format_length_save;
+    }
+  } save_warn_format_settings;
+
   format_check_context format_ctx;
   unsigned HOST_WIDE_INT arg_num;
   tree format_tree;
   format_check_results res;
+
+  /* Buffer size argument number (snprintf and vsnprintf).  */
+  unsigned idx_size = -1;
+
+  /* Object size argument number (snprintf_chk and vsnprintf_chk).  */
+  unsigned idx_objsize = -1;
+
+  if (fndecl && DECL_BUILT_IN (fndecl)
+      && DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_NORMAL)
+    {
+      /* Save the function code to use in diagnostics.  */
+      info->fncode = DECL_FUNCTION_CODE (fndecl);
+
+      switch (DECL_FUNCTION_CODE (fndecl))
+	{
+	case BUILT_IN_SNPRINTF:
+	  idx_size = 2;
+	  info->bounded = true;
+	  break;
+
+	case BUILT_IN_SNPRINTF_CHK:
+	  idx_size = 2;
+	  idx_objsize = 4;
+	  info->bounded = true;
+	  break;
+
+	case BUILT_IN_SPRINTF_CHK:
+	  idx_objsize = 3;
+	  break;
+
+	case BUILT_IN_VSNPRINTF:
+	  idx_size = 2;
+	  info->bounded = true;
+	  break;
+
+	case BUILT_IN_VSNPRINTF_CHK:
+	  idx_size = 2;
+	  idx_objsize = 4;
+	  info->bounded = true;
+	  break;
+
+	case BUILT_IN_VSPRINTF_CHK:
+	  idx_objsize = 3;
+	  break;
+
+	default:
+	  break;
+	}
+    }
+
+  /* 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;
+
   /* Skip to format argument.  If the argument isn't available, there's
      no work for us to do; prototype checking will catch the problem.  */
   for (arg_num = 1; ; ++arg_num)
     {
       if (params == 0)
 	return;
+
+      /* Assume the format string argument never appears before the object
+	 size argument in any of the checked signatures.  */
       if (arg_num == info->format_num)
-	break;
+	  break;
+
+      if (arg_num == idx_size)
+	{
+	  tree size = TREE_VALUE (params);
+	  if (TREE_CODE (size) == INTEGER_CST)
+	    {
+	      dstsize = tree_to_uhwi (size);
+	      if (dstsize > ~(unsigned HOST_WIDE_INT)0 / 2)
+		warning_at (EXPR_LOC_OR_LOC (params, input_location),
+			    0, "specified destination size %qwu 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 (arg_num == idx_objsize)
+	{
+	  /* This is in all likelihood the result of __builtin_object_size
+	     which should be constant but there's a small change that it
+	     isn't.  */
+	  tree size = TREE_VALUE (params);
+	  if (TREE_CODE (size) == INTEGER_CST)
+	    objsize = tree_to_uhwi (size);
+	}
+
       params = TREE_CHAIN (params);
     }
   format_tree = TREE_VALUE (params);
@@ -1405,6 +2126,28 @@  check_format_info (function_format_info *info, tree params)
   if (format_tree == 0)
     return;
 
+  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 = ~(unsigned HOST_WIDE_INT)0;
+    }
+  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 (dstsize != ~(unsigned HOST_WIDE_INT)0 && objsize < dstsize)
+	{
+	  warning (0, "specified destination size %qwu exceeds "
+		   "the size of the object %qwu", dstsize, objsize);
+	  
+	}
+    }
+
   res.number_non_literal = 0;
   res.number_extra_args = 0;
   res.extra_arg_loc = UNKNOWN_LOCATION;
@@ -1414,6 +2157,10 @@  check_format_info (function_format_info *info, tree params)
   res.number_unterminated = 0;
   res.number_other = 0;
   res.format_string_loc = input_location;
+  res.number_chars = 0;
+  res.number_chars_min = 0;
+  res.number_chars_max = 0;
+  res.bounded = true;
 
   format_ctx.res = &res;
   format_ctx.info = info;
@@ -1688,6 +2435,42 @@  check_format_arg (void *ctx, tree format_tree,
 			  params, arg_num, fwt_pool);
 }
 
+/* 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 (const function_format_info *info,
+		 const format_check_results *res)
+{
+  size_t navail = info->objsize;
+
+  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;
+}
 
 /* Do the main part of checking a call to a format function.  FORMAT_CHARS
    is the NUL-terminated format string (which at this point may contain
@@ -1725,7 +2508,7 @@  check_format_info_main (format_check_results *res,
       enum format_lengths length_chars_val = FMT_LEN_none;
       enum format_std_version length_chars_std = STD_C89;
       int format_char;
-      tree cur_param;
+      tree cur_param = NULL_TREE;
       tree wanted_type;
       int main_arg_num = 0;
       tree main_arg_params = 0;
@@ -1738,13 +2521,53 @@  check_format_info_main (format_check_results *res,
       format_wanted_type *last_wanted_type = NULL;
       const format_length_info *fli = NULL;
       const format_char_info *fci = NULL;
+      conversion_spec cvtspec = conversion_spec ();
       char flag_chars[256];
       int alloc_flag = 0;
       int scalar_identity_flag = 0;
       const char *format_start;
 
       if (*format_chars++ != '%')
-	continue;
+	{
+	  /* There must always be at least one byte of space in the buffer
+	     for the terminating NUL that's appended after the format string
+	     has been processed.  */
+	  size_t navail = bytes_remaining (info, res);
+	  /* The destination will have already overflowed if the number of
+	     bytes has wrapped around zero.  */
+	  bool overflowed = SIZE_MAX / 2 <= navail;
+	  if (!overflowed && navail < 1)
+	    {
+	      unsigned long off
+		= (unsigned long)(format_chars - orig_format_chars);
+
+	      location_t loc
+		= location_from_offset (format_string_loc, off);
+
+	      /* Differentiate between an exact and inexact buffer overflow
+		 or truncation.  */
+	      const char *fmtstr;
+	      if (res->number_chars < 0)
+		fmtstr = info->bounded
+		  ? "output may be truncated at or before format character "
+		    "%qc at offset %qlu past the end of a region of size %qlu"
+		  : "writing format character %qc at offset %qlu "
+		    "in a region of size %qlu";
+	      else
+		fmtstr = info->bounded
+		  ? "output truncated at format character %qc at offset %qlu "
+		    "just past the end of a region of size %qlu"
+		  : "writing format character %qc at offset %qlu "
+		    "just past the end of a region of size %qlu";
+	      warning_at (loc, OPT_Wformat_length_, fmtstr,
+			  format_chars [-1], off - 1,
+			  (unsigned long)info->objsize);
+	    }
+
+	  res->inc_number_chars ();
+	  continue;
+	}
+
       if (*format_chars == 0)
 	{
           warning_at (location_from_offset (format_string_loc,
@@ -1755,9 +2578,16 @@  check_format_info_main (format_check_results *res,
 	}
       if (*format_chars == '%')
 	{
+	  res->inc_number_chars ();
 	  ++format_chars;
 	  continue;
 	}
+
+      /* Save the place of the first character of the format conversion
+	 specification so that the full directive can be printed in
+	 diagnostics.  */
+      const char *cvtbeg = format_chars;
+
       flag_chars[0] = 0;
 
       if ((fki->flags & (int) FMT_FLAG_USE_DOLLAR) && has_operand_number != 0)
@@ -1805,6 +2635,8 @@  check_format_info_main (format_check_results *res,
 	      i = strlen (flag_chars);
 	      flag_chars[i++] = *format_chars;
 	      flag_chars[i] = 0;
+
+	      cvtspec.set_flag (*format_chars);
 	    }
 	  if (s->skip_next_char)
 	    {
@@ -1859,6 +2691,7 @@  check_format_info_main (format_check_results *res,
                   else
                     {
                       cur_param = TREE_VALUE (params);
+		      cvtspec.star_width = cur_param;
                       if (has_operand_number <= 0)
                         {
                           params = TREE_CHAIN (params);
@@ -1892,23 +2725,25 @@  check_format_info_main (format_check_results *res,
 	      /* Possibly read a numeric width.  If the width is zero,
 		 we complain if appropriate.  */
 	      int non_zero_width_char = FALSE;
-	      int found_width = FALSE;
+	      cvtspec.have_width = FALSE;
+	      const char *widthbeg = format_chars;
 	      while (ISDIGIT (*format_chars))
 		{
-		  found_width = TRUE;
+		  cvtspec.have_width = TRUE;
 		  if (*format_chars != '0')
 		    non_zero_width_char = TRUE;
 		  ++format_chars;
 		}
-	      if (found_width && !non_zero_width_char &&
+	      if (cvtspec.have_width && !non_zero_width_char &&
 		  (fki->flags & (int) FMT_FLAG_ZERO_WIDTH_BAD))
 		warning_at (format_string_loc, OPT_Wformat_,
 			    "zero width in %s format", fki->name);
-	      if (found_width)
+	      if (cvtspec.have_width)
 		{
 		  i = strlen (flag_chars);
 		  flag_chars[i++] = fki->width_char;
 		  flag_chars[i] = 0;
+		  cvtspec.width = strtol (widthbeg, 0, 10);
 		}
 	    }
 	}
@@ -1925,8 +2760,12 @@  check_format_info_main (format_check_results *res,
 					      format_chars - orig_format_chars),
 			OPT_Wformat_,
 			"empty left precision in %s format", fki->name);
+
+	  const char *precbeg = format_chars;
 	  while (ISDIGIT (*format_chars))
 	    ++format_chars;
+	  cvtspec.precision = strtol (precbeg, 0, 10);
+	  cvtspec.have_precision = 1;
 	}
 
       /* Read any format precision, possibly * or *m$.  */
@@ -1970,6 +2809,7 @@  check_format_info_main (format_check_results *res,
                   else
                     {
                       cur_param = TREE_VALUE (params);
+		      cvtspec.star_precision = cur_param;
                       if (has_operand_number <= 0)
                         {
                           params = TREE_CHAIN (params);
@@ -2006,8 +2846,15 @@  check_format_info_main (format_check_results *res,
 						  format_chars - orig_format_chars),
 			    OPT_Wformat_,
 			    "empty precision in %s format", fki->name);
+
+	      const char *precbeg = format_chars;
 	      while (ISDIGIT (*format_chars))
 		++format_chars;
+	      if (format_chars - precbeg)
+		{
+		  cvtspec.precision = strtol (precbeg, 0, 10);
+		  cvtspec.have_precision = TRUE;
+		}
 	    }
 	}
 
@@ -2047,7 +2894,7 @@  check_format_info_main (format_check_results *res,
 	{
 	  while (fli->name != 0
  		 && strncmp (fli->name, format_chars, strlen (fli->name)))
-	      fli++;
+	    fli++;
 	  if (fli->name != 0)
 	    {
  	      format_chars += strlen (fli->name);
@@ -2057,6 +2904,7 @@  check_format_info_main (format_check_results *res,
 		  length_chars = fli->double_name;
 		  length_chars_val = fli->double_index;
 		  length_chars_std = fli->double_std;
+		  cvtspec.modifier = fli->double_index;
 		}
 	      else
 		{
@@ -2064,6 +2912,7 @@  check_format_info_main (format_check_results *res,
 		  length_chars_val = fli->index;
 		  length_chars_std = fli->std;
 		  scalar_identity_flag = fli->scalar_identity_flag;
+		  cvtspec.modifier = fli->index;
 		}
 	      i = strlen (flag_chars);
 	      flag_chars[i++] = fki->length_code_char;
@@ -2106,6 +2955,7 @@  check_format_info_main (format_check_results *res,
 	    }
 	}
 
+      /* Read the conversion specifier character.  */
       format_char = *format_chars;
       if (format_char == 0
 	  || (!(fki->flags & (int) FMT_FLAG_FANCY_PERCENT_OK)
@@ -2117,6 +2967,7 @@  check_format_info_main (format_check_results *res,
 		      "conversion lacks type at end of format");
 	  continue;
 	}
+
       format_chars++;
       fci = fki->conversion_specs;
       while (fci->format_chars != 0
@@ -2148,6 +2999,8 @@  check_format_info_main (format_check_results *res,
 			C_STD_NAME (fci->std), format_char, fki->name);
 	}
 
+      cvtspec.specifier = format_char;
+
       /* Validate the individual flags used, removing any that are invalid.  */
       {
 	int d = 0;
@@ -2327,7 +3180,14 @@  check_format_info_main (format_check_results *res,
 
       /* Finally. . .check type of argument against desired type!  */
       if (info->first_arg_num == 0)
-	continue;
+	{
+	  if (warn_format_length)
+	    check_format_length (format_string_loc, res, info, fci,
+				 cvtbeg, format_chars - cvtbeg,
+				 cvtbeg - orig_format_chars,
+				 &cvtspec, NULL_TREE);
+	  continue;
+	}
       if ((fci->pointer_count == 0 && wanted_type == void_type_node)
 	  || suppressed)
 	{
@@ -2366,7 +3226,7 @@  check_format_info_main (format_check_results *res,
 	    }
 
 	  wanted_type_ptr = &main_wanted_type;
-	  while (fci)
+	  for (const format_char_info *pfci = fci; pfci; )
 	    {
 	      if (params == 0)
                 cur_param = NULL;
@@ -2378,9 +3238,9 @@  check_format_info_main (format_check_results *res,
 
 	      wanted_type_ptr->wanted_type = wanted_type;
 	      wanted_type_ptr->wanted_type_name = wanted_type_name;
-	      wanted_type_ptr->pointer_count = fci->pointer_count + alloc_flag;
+	      wanted_type_ptr->pointer_count = pfci->pointer_count + alloc_flag;
 	      wanted_type_ptr->char_lenient_flag = 0;
-	      if (strchr (fci->flags2, 'c') != 0)
+	      if (strchr (pfci->flags2, 'c') != 0)
 		wanted_type_ptr->char_lenient_flag = 1;
 	      wanted_type_ptr->scalar_identity_flag = 0;
 	      if (scalar_identity_flag)
@@ -2391,9 +3251,9 @@  check_format_info_main (format_check_results *res,
 		wanted_type_ptr->writing_in_flag = 1;
 	      else
 		{
-		  if (strchr (fci->flags2, 'W') != 0)
+		  if (strchr (pfci->flags2, 'W') != 0)
 		    wanted_type_ptr->writing_in_flag = 1;
-		  if (strchr (fci->flags2, 'R') != 0)
+		  if (strchr (pfci->flags2, 'R') != 0)
 		    wanted_type_ptr->reading_from_flag = 1;
 		}
               wanted_type_ptr->kind = CF_KIND_FORMAT;
@@ -2409,19 +3269,25 @@  check_format_info_main (format_check_results *res,
 		first_wanted_type = wanted_type_ptr;
 	      last_wanted_type = wanted_type_ptr;
 
-	      fci = fci->chain;
-	      if (fci)
+	      pfci = pfci->chain;
+	      if (pfci)
 		{
 		  wanted_type_ptr = fwt_pool.allocate ();
 		  arg_num++;
-		  wanted_type = *fci->types[length_chars_val].type;
-		  wanted_type_name = fci->types[length_chars_val].name;
+		  wanted_type = *pfci->types[length_chars_val].type;
+		  wanted_type_name = pfci->types[length_chars_val].name;
 		}
 	    }
 	}
 
       if (first_wanted_type != 0)
         check_format_types (format_string_loc, first_wanted_type);
+
+      if (warn_format_length)
+	check_format_length (format_string_loc, res, info, fci,
+			     cvtbeg, format_chars - cvtbeg,
+			     cvtbeg - orig_format_chars,
+			     &cvtspec, cur_param);
     }
 
   if (format_chars - orig_format_chars != format_length)
@@ -2437,6 +3303,62 @@  check_format_info_main (format_check_results *res,
     }
   if (has_operand_number > 0)
     finish_dollar_format_checking (res, fki->flags & (int) FMT_FLAG_DOLLAR_GAP_POINTER_OK);
+
+  /* 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
+     formt directives or arguments encountered during processing
+     that prevent length tracking with any reliability.  */
+  if (!warn_format_length
+      || SIZE_MAX / 2 < info->objsize
+      || res->number_chars_min < 0)
+    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.  */
+  size_t navail = bytes_remaining (info, 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"), a certain overlow somewhere "past
+     the end", and a certain overflow into the byte "just past the end"
+     of the destination.  (Ditto for truncation.)  */
+  bool overflowed = SIZE_MAX / 2 <= navail;
+  if (!overflowed && navail == 0)
+    {
+      location_t loc
+	= location_from_offset (format_string_loc,
+				format_chars - orig_format_chars + 1);
+
+      bool boundrange = res->number_chars_min < 0
+	|| (res->number_chars_min < res->number_chars_max);
+
+      const char* fmtstr = info->bounded
+	? (res->number_chars < 0 && boundrange
+	   ? "output may be truncated while copying format string "
+	     "into a region of size %qlu"
+	   : "output truncated while copying format string "
+	     "into a region of size %qlu")
+	: (res->number_chars < 0 ? boundrange
+	   ? "may write a terminating nul past the end "
+	     "of a region of size %qlu"
+	   : "writing a terminating nul past the end "
+	     "of a region of size %qlu"
+	   : "writing a terminating nul just past the end "
+	     "of a region of size %qlu");
+
+      warning_at (loc, OPT_Wformat_length_, fmtstr,
+		  (unsigned long)info->objsize);
+    }
+
+  /* Help the user figure out how big a buffer they need.  */
+  if (warn_format_length && (overflowed || navail < 1))
+    inform (input_location,
+	    "destination region size is %qwu bytes, minimum required %qwu",
+	    (unsigned HOST_WIDE_INT)info->objsize,
+	    (unsigned HOST_WIDE_INT)(info->objsize - navail + 1));
 }
 
 
@@ -2617,6 +3539,124 @@  check_format_types (location_t loc, format_wanted_type *types)
     }
 }
 
+static void
+check_format_length (location_t                  loc,
+		     format_check_results       *res,
+		     const function_format_info *info,
+		     const format_char_info     *fci,
+		     const char                 *cvtbeg,
+		     size_t                      cvtlen,
+		     size_t                      offset,
+		     const conversion_spec      *cvtspec,
+		     tree                        arg)
+{
+  /* 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 (!fci->fmtfunc
+      || SIZE_MAX / 2 < info->objsize
+      || res->number_chars_min == -1)
+    return;
+
+  /* Compute the (approximate) length of the formatted output.  */
+  format_char_info::fmtresult fmtres = fci->fmtfunc (cvtspec, arg);
+
+  /* The overall result is bounded only if the output of every
+     directive is exact or bounded.  */
+  res->bounded = fmtres.bounded;
+
+  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.  */
+  size_t navail = bytes_remaining (info, res);
+
+  /* The destination will have already overflowed if the number of
+     bytes has wrapped around zero.  */
+  bool overflowed = SIZE_MAX / 2 <= navail;
+
+  if (fmtres.min < fmtres.max)
+    {
+      /* The result is a range (i.e., it's inexact).  */
+      if (!overflowed)
+	{
+	  loc = location_from_offset (loc, offset);
+
+	  if (navail < (size_t)fmtres.min)
+	    {
+	      const char* fmtstr = info->bounded
+		? "%<%%%.*s%> directive output truncated %qi bytes "
+		  "into a region of size %qlu"
+		: "%<%%%.*s%> directive writing at least %qi bytes "
+		  "into a region of size %qlu";
+		warning_at (loc, OPT_Wformat_length_, fmtstr,
+			    (int)cvtlen, cvtbeg, fmtres.min,
+			    (unsigned long)navail);
+	    }
+	  else if (navail < (size_t)fmtres.max
+		   && (fmtres.bounded || 1 < warn_format_length))
+	    {
+	      const char* fmtstr = info->bounded
+		? "%<%%%.*s%> directive output may be truncated between "
+		  "%qi and %qi bytes into a region of size %qlu"
+		: "%<%%%.*s%> directive writing between %qi and %qi bytes "
+		  "into a region of size %qlu";
+	      warning_at (loc, OPT_Wformat_length_, fmtstr,
+			  (int)cvtlen, cvtbeg, fmtres.min, fmtres.max,
+			  (unsigned long)navail);
+	    }
+	}
+
+      /* 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 < (size_t)fmtres.min)
+	{
+	  loc = location_from_offset (loc, offset);
+	  const char* fmtstr;
+	  if (info->bounded)
+	    fmtstr = 1 < fmtres.min
+	      ? "%<%%%.*s%> directive output truncated while writing "
+	        "%qi bytes into a region of size %qlu"
+	      : "%<%%%.*s%> directive output truncated while writing "
+	        "%qi byte into a region of size %qlu";
+	  else
+	    fmtstr = 1 < fmtres.min
+	      ? "%<%%%.*s%> directive writing %qi bytes "
+	        "into a region of size %qlu"
+	      : "%<%%%.*s%> directive writing %qi byte "
+	        "into a region of size %qlu";
+	  warning_at (loc, OPT_Wformat_length_, fmtstr,
+		      (int)cvtlen, cvtbeg, fmtres.min, (unsigned long)navail);
+	}
+      res->inc_number_chars (fmtres.min);
+    }
+}
 
 /* Give a warning at LOC about a format argument of different type from that
    expected.  WANTED_TYPE is the type the argument should have, possibly
diff --git a/gcc/c-family/c-format.h b/gcc/c-family/c-format.h
index edbd4a1..8a34c8a 100644
--- a/gcc/c-family/c-format.h
+++ b/gcc/c-family/c-format.h
@@ -126,6 +126,7 @@  struct format_type_detail
 #define BADLEN	{ STD_C89, NULL, NULL }
 #define NOLENGTHS	{ BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }
 
+struct conversion_spec;
 
 /* Structure describing a format conversion specifier (or a set of specifiers
    which act identically), and the length modifiers used with it.  */
@@ -158,6 +159,17 @@  struct format_char_info
      arguments, only POINTER_COUNT, TYPES, and the "c", "R", and "W" flags
      in FLAGS2 are used.  */
   const struct format_char_info *chain;
+
+  /* Function to convert an argument for the format characters
+     in FORMAT_CHARS and return the number of characters.   */
+  struct fmtresult {
+    int max, min;
+    /* True when the range is the result of an argument determined
+       to be bounded to a subrange of its type, false otherwise.  */
+    bool bounded;
+  };
+
+  fmtresult (*fmtfunc)(const conversion_spec *, tree);
 };
 
 
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 761a83b..f2e82e2 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/c/c-lang.c b/gcc/c/c-lang.c
index 89954b7..9841787 100644
--- a/gcc/c/c-lang.c
+++ b/gcc/c/c-lang.c
@@ -37,6 +37,8 @@  enum c_language_kind c_language = clk_c;
 #define LANG_HOOKS_INIT c_objc_common_init
 #undef LANG_HOOKS_INIT_TS
 #define LANG_HOOKS_INIT_TS c_common_init_ts
+#undef LANG_HOOKS_CHECK_FORMAT_LENGTH
+#define LANG_HOOKS_CHECK_FORMAT_LENGTH check_function_format
 
 /* Each front end provides its own lang hook initializer.  */
 struct lang_hooks lang_hooks = LANG_HOOKS_INITIALIZER;
diff --git a/gcc/c/c-typeck.c b/gcc/c/c-typeck.c
index a681d76..53288b2 100644
--- a/gcc/c/c-typeck.c
+++ b/gcc/c/c-typeck.c
@@ -3090,7 +3090,7 @@  build_function_call_vec (location_t loc, vec<location_t> arg_loc,
     return error_mark_node;
 
   /* Check that the arguments to the function are valid.  */
-  check_function_arguments (loc, fntype, nargs, argarray);
+  check_function_arguments (loc, fundecl, fntype, nargs, argarray);
 
   if (name != NULL_TREE
       && !strncmp (IDENTIFIER_POINTER (name), "__builtin_", 10))
diff --git a/gcc/cp/call.c b/gcc/cp/call.c
index 729b7eb..f3d397c 100644
--- a/gcc/cp/call.c
+++ b/gcc/cp/call.c
@@ -7578,7 +7578,8 @@  build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
       for (j = 0; j < nargs; j++)
 	fargs[j] = maybe_constant_value (argarray[j]);
 
-      check_function_arguments (input_location, TREE_TYPE (fn), nargs, fargs);
+      check_function_arguments (input_location, fn, TREE_TYPE (fn),
+				nargs, fargs);
     }
 
   /* Avoid actually calling copy constructors and copy assignment operators,
diff --git a/gcc/cp/cp-lang.c b/gcc/cp/cp-lang.c
index 8cfee4f..bb8a296 100644
--- a/gcc/cp/cp-lang.c
+++ b/gcc/cp/cp-lang.c
@@ -78,6 +78,8 @@  static tree cxx_enum_underlying_base_type (const_tree);
 #define LANG_HOOKS_EH_RUNTIME_TYPE build_eh_type_type
 #undef LANG_HOOKS_ENUM_UNDERLYING_BASE_TYPE
 #define LANG_HOOKS_ENUM_UNDERLYING_BASE_TYPE cxx_enum_underlying_base_type
+#undef LANG_HOOKS_CHECK_FORMAT_LENGTH
+#define LANG_HOOKS_CHECK_FORMAT_LENGTH check_function_format
 
 /* Each front end provides its own lang hook initializer.  */
 struct lang_hooks lang_hooks = LANG_HOOKS_INITIALIZER;
diff --git a/gcc/cp/typeck.c b/gcc/cp/typeck.c
index f68c2a3..93c9ddb 100644
--- a/gcc/cp/typeck.c
+++ b/gcc/cp/typeck.c
@@ -3641,7 +3641,7 @@  cp_build_function_call_vec (tree function, vec<tree, va_gc> **params,
 
   /* Check for errors in format strings and inappropriately
      null parameters.  */
-  check_function_arguments (input_location, fntype, nargs, argarray);
+  check_function_arguments (input_location, fndecl, fntype, nargs, argarray);
 
   ret = build_cxx_call (function, nargs, argarray, complain);
 
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index aa11209..67c64eb 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -265,7 +265,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
@@ -3773,6 +3774,80 @@  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 argument that are known to
+be bounded to a subrange of their type 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/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/langhooks-def.h b/gcc/langhooks-def.h
index 034b3b7..ff7b4d1 100644
--- a/gcc/langhooks-def.h
+++ b/gcc/langhooks-def.h
@@ -52,6 +52,7 @@  extern void lhd_print_error_function (diagnostic_context *,
 				      const char *, struct diagnostic_info *);
 extern void lhd_set_decl_assembler_name (tree);
 extern bool lhd_warn_unused_global_decl (const_tree);
+extern void lhd_check_format_length (const_tree, const_tree, int, tree *);
 extern void lhd_incomplete_type_error (location_t, const_tree, const_tree);
 extern tree lhd_type_promotes_to (tree);
 extern void lhd_register_builtin_type (tree, const char *);
@@ -211,6 +212,7 @@  extern tree lhd_make_node (enum tree_code);
 #define LANG_HOOKS_FUNCTION_DECL_EXPLICIT_P hook_bool_tree_false
 #define LANG_HOOKS_FUNCTION_DECL_DELETED_P hook_bool_tree_false
 #define LANG_HOOKS_WARN_UNUSED_GLOBAL_DECL lhd_warn_unused_global_decl
+#define LANG_HOOKS_CHECK_FORMAT_LENGTH lhd_check_format_length
 #define LANG_HOOKS_POST_COMPILATION_PARSING_CLEANUPS NULL
 #define LANG_HOOKS_DECL_OK_FOR_SIBCALL	lhd_decl_ok_for_sibcall
 #define LANG_HOOKS_OMP_PRIVATIZE_BY_REFERENCE hook_bool_const_tree_false
@@ -236,6 +238,7 @@  extern tree lhd_make_node (enum tree_code);
   LANG_HOOKS_FUNCTION_PARM_EXPANDED_FROM_PACK_P, \
   LANG_HOOKS_GET_GENERIC_FUNCTION_DECL, \
   LANG_HOOKS_WARN_UNUSED_GLOBAL_DECL, \
+  LANG_HOOKS_CHECK_FORMAT_LENGTH, \
   LANG_HOOKS_POST_COMPILATION_PARSING_CLEANUPS, \
   LANG_HOOKS_DECL_OK_FOR_SIBCALL, \
   LANG_HOOKS_OMP_PRIVATIZE_BY_REFERENCE, \
diff --git a/gcc/langhooks.c b/gcc/langhooks.c
index 3256a9d..52e29f2 100644
--- a/gcc/langhooks.c
+++ b/gcc/langhooks.c
@@ -136,6 +136,14 @@  lhd_warn_unused_global_decl (const_tree decl)
   return true;
 }
 
+void
+lhd_check_format_length (const_tree ARG_UNUSED (fndecl),
+			 const_tree ARG_UNUSED (attrs),
+			 int ARG_UNUSED (nargs),
+			 tree * ARG_UNUSED (argarray))
+{
+}
+
 /* Set the DECL_ASSEMBLER_NAME for DECL.  */
 void
 lhd_set_decl_assembler_name (tree decl)
diff --git a/gcc/langhooks.h b/gcc/langhooks.h
index 0593424..d5c5257 100644
--- a/gcc/langhooks.h
+++ b/gcc/langhooks.h
@@ -201,6 +201,9 @@  struct lang_hooks_for_decls
      We will already have checked that it has static binding.  */
   bool (*warn_unused_global) (const_tree);
 
+  /* Check the length of output produced by sprintf-like functions.  */
+  void (*check_format_length) (const_tree, const_tree, int, tree *);
+
   /* Perform any post compilation-proper parser cleanups and
      processing.  This is currently only needed for the C++ parser,
      which hopefully can be cleaned up so this hook is no longer
diff --git a/gcc/passes.c b/gcc/passes.c
index 0565cfa..008799c 100644
--- a/gcc/passes.c
+++ b/gcc/passes.c
@@ -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/testsuite/gcc.dg/atomic/pr71675.c b/gcc/testsuite/gcc.dg/atomic/pr71675.c
new file mode 100644
index 0000000..0e344ac
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/atomic/pr71675.c
@@ -0,0 +1,32 @@ 
+/* PR c++/71675 - __atomic_compare_exchange_n returns wrong type for typed enum
+ */
+/* { dg-do compile { target c11 } } */
+
+#define Test(T)								\
+  do {									\
+    static T x;								\
+    int r [_Generic (__atomic_compare_exchange_n (&x, &x, x, 0, 0, 0),	\
+		     _Bool: 1, default: -1)];				\
+    (void)&r;								\
+  } while (0)
+
+void f (void)
+{
+  /* __atomic_compare_exchange_n would fail to return _Bool when
+     its arguments were one of the three character types. */
+  Test (char);
+  Test (signed char);
+  Test (unsigned char);
+
+  Test (int);
+  Test (unsigned int);
+
+  Test (long);
+  Test (unsigned long);
+
+  Test (long long);
+  Test (unsigned long long);
+
+  typedef enum E { e } E;
+  Test (E);
+}
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..80b3455
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/format/c99-sprintf-length-1.c
@@ -0,0 +1,852 @@ 
+/* { 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.  */
+#ifndef LINE
+# define LINE 0
+#endif
+
+typedef __SIZE_TYPE__ size_t;
+
+#if !__cplusplus
+typedef __WCHAR_TYPE__ wchar_t;
+#endif
+
+typedef unsigned char UChar;
+
+#define T(bufsize, fmt, ...)						\
+  __builtin___sprintf_chk (buffer, 0,					\
+      (!LINE || __LINE__ == LINE) ? bufsize : __SIZE_MAX__, fmt, __VA_ARGS__)
+
+char buffer [256];
+
+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";
+
+/* Exercise the "%c" and "%lc" directive.  */
+
+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 just past the end of a region of size .1." } */
+  T (1, "%c",   '1');            /* { dg-warning "nul just past the end" } */
+  T (2, "%c",   '1');
+  T (2, "%2c",  '1');            /* { dg-warning "nul just past the end" } */
+  T (2, "%3c",  '1');            /* { dg-warning "into a region" } */
+  T (2, "%c%c", '1', '2');       /* { dg-warning "nul just 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 just 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.  */
+
+void test_sprintf_chk_s_const (void)
+{
+  T (0, "%*s",  0, "");         /* { dg-warning "nul just past the end" } */
+  T (0, "%*s",  0, s0);         /* { dg-warning "nul just 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 just past the end" } */
+  T (1, "%*s",  0, s1);         /* { dg-warning "nul just past the end" } */
+  T (1, "%1s",     "");         /* { dg-warning "nul just past the end" } */
+  T (1, "%1s",     s0);         /* { dg-warning "nul just past the end" } */
+  T (1, "%*s",  1, "");         /* { dg-warning "nul just past the end" } */
+  T (1, "%*s",  1, s0);         /* { dg-warning "nul just 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 just past the end" } */
+  T (1, "%.1s",    s3);         /* { dg-warning "nul just past the end" } */
+  T (1, "%.*s", 1, "123");      /* { dg-warning "nul just past the end" } */
+  T (1, "%.*s", 1, s3);         /* { dg-warning "nul just 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");       /* { dg-warning "nul just past the end" } */
+  T (2, "%*s",  0, s2);         /* { dg-warning "nul just past the end" } */
+
+  T (1, "%s%s", "", "");
+  T (1, "%s%s", s0, s0);
+  T (1, "%s%s", "", "1");       /* { dg-warning "nul just past the end" } */
+  T (1, "%s%s", s0, s1);        /* { dg-warning "nul just past the end" } */
+  T (1, "%s%s", "1", "");       /* { dg-warning "nul just past the end" } */
+  T (1, "%s%s", s1, s0);        /* { dg-warning "nul just 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 just past the end" } */
+  T (2, "%s%s", "1", "2");      /* { dg-warning "nul just 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 just past the end" } */
+  T (2, "%%%s",  "1");          /* { dg-warning "nul just past the end" } */
+  T (2, "%s%%",  "1");          /* { dg-warning "nul just past the end" } */
+  T (2, "_%s",   "12");         /* { dg-warning "into a region" } */
+  T (2, "__%s",  "1");          /* { dg-warning "into a region" } */
+
+  T (3, "__%s", "");
+  T (3, "__%s", "1");           /* { dg-warning "nul just 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 just 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, "%.2ls",    L"1");      /* { dg-warning "may write a terminating 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.  */
+
+void test_sprintf_chk_hh_const (void)
+{
+  T (1, "%hhd",         0);     /* { dg-warning "nul just past the end" } */
+  T (1, "%hhd",         1);     /* { dg-warning "nul just 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 just past the end" } */
+
+  T (1, "%hhi",         0);     /* { dg-warning "nul just past the end" } */
+  T (1, "%hhi",         1);     /* { dg-warning "nul just 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 just past the end" } */
+
+  T (2, "%hhi",         0);
+  T (2, "%hhi",         1);
+  T (2, "%hhi",         9);
+  T (2, "% hhi",        9);     /* { dg-warning "nul just past the end" } */
+  T (2, "%+hhi",        9);     /* { dg-warning "nul just past the end" } */
+  T (2, "%-hhi",        9);
+  T (2, "%hhi",        10);     /* { dg-warning "nul just past the end" } */
+  T (2, "%hhi",        -1);     /* { dg-warning "nul just past the end" } */
+  T (2, "% hhi",       -1);     /* { dg-warning "nul just past the end" } */
+  T (2, "%+hhi",       -1);     /* { dg-warning "nul just past the end" } */
+  T (2, "%-hhi",       -1);     /* { dg-warning "nul just past the end" } */
+
+  T (2, "%hho",         0);
+  T (2, "%hho",         1);
+  T (2, "%hho",         7);
+  T (2, "%hho",       010);     /* { dg-warning "nul just past the end" } */
+  T (2, "%hho",       077);     /* { dg-warning "nul just 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 just past the end" } */
+  T (2, "%hhX",       0xf);
+  T (2, "%hhx",      0x10);     /* { dg-warning "nul just past the end" } */
+  T (2, "%hhX",      0xff);     /* { dg-warning "nul just past the end" } */
+
+  T (1, "%#hhx",        0);     /* { dg-warning "nul just past the end" } */
+  T (2, "%#hhx",        0);
+  T (3, "%#hhx",        1);     /* { dg-warning "nul just 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 just past the end" } */
+  T (4, "%#hhx",      0xf);
+  T (4, "%#hhx",     0x10);     /* { dg-warning "nul just past the end" } */
+  T (4, "%#hhx",     0xff);     /* { dg-warning "nul just past the end" } */
+  T (4, "%#hhx",    0xfff);     /* { dg-warning "nul just past the end" } */
+
+  T (4, "%hhi %hhi",  0,  0);
+  T (4, "%hhi %hhi",  9,  9);
+  T (4, "%hhi %hhi",  1, 10);   /* { dg-warning "nul just past the end" } */
+  T (4, "%hhi %hhi", 10,  1);   /* { dg-warning "nul just past the end" } */
+  T (4, "%hhi %hhi", 11, 12);   /* { dg-warning "into a region" } */
+
+  /* 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" } */
+}
+
+void test_sprintf_chk_h_const (void)
+{
+  T (1, "%hu",          0);     /* { dg-warning "nul just past the end" } */
+  T (1, "%hu",          1);     /* { dg-warning "nul just 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 just past the end" } */
+  T (2, "%+hi",         9);     /* { dg-warning "nul just past the end" } */
+  T (2, "%-hi",         9);
+  T (2, "%hi",         10);     /* { dg-warning "nul just past the end" } */
+  T (2, "%hi",         -1);     /* { dg-warning "nul just past the end" } */
+  T (2, "% hi",        -2);     /* { dg-warning "nul just past the end" } */
+  T (2, "%+hi",        -3);     /* { dg-warning "nul just past the end" } */
+  T (2, "%-hi",        -4);     /* { dg-warning "nul just past the end" } */
+
+  T (2, "%hu",          0);
+  T (2, "%hu",          1);
+  T (2, "%hu",          9);
+  T (2, "%hu",         10);     /* { dg-warning "nul just 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 just past the end" } */
+  T (2, "%ho",        077);     /* { dg-warning "nul just 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 just past the end" } */
+  T (2, "%hx",       0xff);     /* { dg-warning "nul just 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 just past the end" } */
+  T (5, "%hu",      65535);     /* { dg-warning "nul just past the end" } */
+
+  T (1, "%#hx",         0);     /* { dg-warning "nul just past the end" } */
+  T (2, "%#hx",         0);
+  T (3, "%#hx",         1);     /* { dg-warning "nul just past the end" } */
+
+  T (4, "%#hx",         0);
+  T (4, "%#hx",         1);
+  T (4, "%#hx",       0xf);
+  T (4, "%#hx",      0x10);     /* { dg-warning "nul just past the end" } */
+  T (4, "%#hx",      0xff);     /* { dg-warning "nul just 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 just past the end" } */
+  T (1, "%hhu",         1);     /* { dg-warning "nul just 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 just past the end" } */
+}
+
+void test_sprintf_chk_integer_const (void)
+{
+  T ( 1, "%i",          0);         /* { dg-warning "nul just past the end" } */
+  T ( 1, "%i",          1);         /* { dg-warning "nul just past the end" } */
+  T ( 1, "%i",         -1);         /* { dg-warning "into a region" } */
+  T ( 1, "%i_",         1);         /* { dg-warning "character ._. at offset .2. just 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 just past the end" } */
+  T ( 1, "%u",          0);         /* { dg-warning "nul just past the end" } */
+  T ( 1, "%x",          0);         /* { dg-warning "nul just past the end" } */
+  T ( 1, "%#x",         0);         /* { dg-warning "nul just past the end" } */
+  T ( 1, "%x",          1);         /* { dg-warning "nul just 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 just past the end" } */
+  T ( 2, "%i",         10);         /* { dg-warning "nul just past the end" } */
+  T ( 2, "%i_",         0);         /* { dg-warning "nul just past the end" } */
+  T ( 2, "_%i",         0);         /* { dg-warning "nul just past the end" } */
+  T ( 2, "_%i_",        0);         /* { dg-warning "character ._. at offset .3. just past the end" } */
+  T ( 2, "%o",          1);
+  T ( 2, "%o",          7);
+  T ( 2, "%o",        010);         /* { dg-warning "nul just 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 just past the end" } */
+  T ( 2, "%x",       0xff);         /* { dg-warning "nul just 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 just 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 just past the end" } */
+  T ( 9, "%8u",         1);
+
+#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 just past the end" } */
+  T (10, "%i",        MAX);         /* { dg-warning "nul just past the end" } */
+  T (10, "%i",        MIN);         /* { dg-warning "into a region" } */
+
+  T (11, "%i",        MAX);
+  T (11, "%i",        MIN);         /* { dg-warning "nul just past the end" } */
+}
+
+void test_sprintf_chk_l_const (void)
+{
+  T ( 1, "%li",        0L);         /* { dg-warning "nul just past the end" } */
+  T ( 1, "%li",        1L);         /* { dg-warning "nul just past the end" } */
+  T ( 1, "%li",       -1L);         /* { dg-warning "into a region" } */
+  T ( 1, "%li_",       1L);         /* { dg-warning "character ._. at offset .3. just past the end" } */
+  T ( 1, "_%li",       1L);         /* { dg-warning "into a region" } */
+  T ( 1, "_%li_",      1L);         /* { dg-warning "into a region" } */
+}
+
+void test_sprintf_chk_z_const (void)
+{
+  T ( 1, "%zi",        (size_t)0);  /* { dg-warning "nul just past the end" } */
+  T ( 1, "%zi",        (size_t)1);  /* { dg-warning "nul just 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. just 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 just 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 just past the end" } */
+  T (12, "%e",  12.0);           /* { dg-warning "nul just past the end" } */
+  T (13, "%e",   1.3);
+  T (13, "%E",  13.0);
+  T (13, "%e",  13.0);
+
+  T ( 5, "%.0e", 0.0);           /* { dg-warning "nul just past the end" } */
+  T ( 5, "%.0e", 1.0);           /* { dg-warning "nul just 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 (const char *s)
+{
+  T (0, "%s",   s);             /* { dg-warning "nul past the end" } */
+  T (1, "%s",   s);
+  /* 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 "just 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 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)
+{
+  T  (0, "%E",          d);           /* { dg-warning "writing at least .13. 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 "into a region" } */
+  T (12, "%e",          d);           /* { dg-warning "into a region" } */
+  T (13, "%E",          d);           /* { dg-warning "nul past the end" } */
+  T (13, "%e",          d);           /* { dg-warning "nul past the end" } */
+  T (14, "%E",          d);
+  T (14, "%e",          d);
+
+  T ( 5, "%.0e",        d);           /* { dg-warning "directive writing at least .6. bytes into a region of size .5." } */
+  T ( 6, "%.0e",        d);           /* { dg-warning "nul past the end" } */
+  T ( 7, "%.0e",        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(bufsize, fmt)							\
+  __builtin___vsprintf_chk (buffer, 0,					\
+      (!LINE || __LINE__ == LINE) ? bufsize : __SIZE_MAX__, 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 just past the end of a region of size .1." } */
+  T (1, "%c");              /* { dg-warning "nul just past the end" } */
+  T (2, "%c");
+  T (2, "%2c");             /* { dg-warning "nul just past the end" } */
+  T (2, "%3c");             /* { dg-warning "into a region" } */
+  T (2, "%c%c");            /* { dg-warning "nul just 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 just 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(bufsize, fmt, ...)						\
+  __builtin_snprintf (buffer,						\
+      (!LINE || __LINE__ == LINE) ? bufsize : __SIZE_MAX__, 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 while copying format string into a region of size .1." } */
+  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 while copying format string" } */
+  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 while copying format string" } */
+
+  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(bufsize, fmt, ...)						\
+  __builtin___snprintf_chk (buffer, bufsize, 0,				\
+      (!LINE || __LINE__ == LINE) ? bufsize : __SIZE_MAX__, 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 "destination size .3. exceeds the size of the object .2." } */
+
+  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 while copying format string into a region of size .1." } */
+  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 while copying format string" } */
+  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 while copying format string" } */
+
+  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(bufsize, fmt)							\
+  __builtin_vsnprintf (buffer,						\
+      (!LINE || __LINE__ == LINE) ? bufsize : __SIZE_MAX__, fmt, va)
+
+void test_vsnprintf_s (__builtin_va_list va)
+{
+  T (0, "%s");
+  T (1, "%s");
+  T (1, "%1s");             /* { dg-warning "output truncated while copying format string" } */
+
+  T (2, "%s%s");
+  T (2, "%s%s_");
+  T (2, "%s_%s");
+  T (2, "_%s%s");
+  T (2, "_%s_%s");          /* { dg-warning "output truncated while copying format string" } */
+}
+
+#undef T
+#define T(bufsize, fmt)							\
+  __builtin___vsnprintf_chk (buffer, bufsize, 0,			\
+      (!LINE || __LINE__ == LINE) ? bufsize : __SIZE_MAX__, 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 "destination size .123. exceeds the size of the object .122." } */
+
+  __builtin___snprintf_chk (buffer, __SIZE_MAX__, 0, 2, " ");   /* { dg-warning "destination size .* too large" } */
+
+  T (0, "%s");
+  T (1, "%s");
+  T (1, "%1s");             /* { dg-warning "output truncated while copying format string" } */
+
+  T (2, "%s%s");
+  T (2, "%s%s_");
+  T (2, "%s_%s");
+  T (2, "_%s%s");
+  T (2, "_%s_%s");          /* { dg-warning "output truncated while copying format string" } */
+}
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..7cba13a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/format/c99-sprintf-length-2.c
@@ -0,0 +1,183 @@ 
+/* { dg-do compile } */
+/* { dg-options "-std=c99 -Wformat -Wformat-length=2 -ftrack-macro-expansion=0" } */
+
+/* Define line to the line number of the test case to exercise and
+   avoid exercising all the others.  */
+#ifndef LINE
+# define LINE 0
+#endif
+
+#if !__cplusplus
+typedef __WCHAR_TYPE__ wchar_t;
+#endif
+
+typedef unsigned char UChar;
+
+#if 1 // TEST_SPRINTF_CHK
+#  define T(bufsize, fmt, ...)						\
+    __builtin___sprintf_chk (buffer, 0,					\
+      (!LINE || __LINE__ == LINE) ? bufsize : __SIZE_MAX__, fmt, __VA_ARGS__)
+#elif TEST_VSNPRINTF_CHK
+#  define T(bufsize, fmt, ...)						\
+  __builtin___vsnprintf_chk (va, buffer, sizeof buffer, 0		\
+      (!LINE || __LINE__ == LINE) ? bufsize : __SIZE_MAX__, fmt, va)
+#elif TEST_VSNPRINTF
+#  define T(bufsize, fmt, ...)						\
+  __builtin_vsnprintf (va, buffer,					\
+      (!LINE || __LINE__ == LINE) ? bufsize : __SIZE_MAX__, fmt, __VA_ARGS__)
+#else
+#  define T(bufsize, fmt, ...)						\
+    __builtin_snprintf (buffer,						\
+      (!LINE || __LINE__ == LINE) ? bufsize : __SIZE_MAX__, fmt, __VA_ARGS__)
+#endif
+
+__builtin_va_list va;
+
+char buffer [256];
+
+/* 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 following case is diagnosed at level 2 but not at level 1.  */
+  T (2, "%.2ls",    L"1");      /* { dg-warning "may write a terminating 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");
+}
+
+/* Exercise buffer overflow detection with non-const string arguments.  */
+
+void test_s_nonconst (const char *s, const wchar_t *ws)
+{
+  T (0, "%s",   s);             /* { dg-warning "into a region" } */
+  T (1, "%s",   s);             /* { dg-warning "nul past the end" } */
+  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" } */
+}
+
+  /* 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 a region of size .4." } */
+}
+
+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) int 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 a region of size .3." } */
+  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 just past the end" } */
+  T (1, "%i",  abf [x].b1);     /* { dg-warning "nul just past the end" } */
+  T (1, "%i",     pbf->b1);     /* { dg-warning "nul just 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-opt.c b/gcc/testsuite/gcc.dg/format/c99-sprintf-length-opt.c
new file mode 100644
index 0000000..2444e5f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/format/c99-sprintf-length-opt.c
@@ -0,0 +1,186 @@ 
+/* { dg-do compile } */
+/* { dg-options "-std=c99 -O2 -Wformat -Wformat-length=1 -ftrack-macro-expansion=0" } */
+
+#ifndef LINE
+#  define LINE 0
+#endif
+
+#define SCHAR_MAX   __SCHAR_MAX__
+#define SCHAR_MIN   (-SCHAR_MAX - 1)
+
+#define bos(x)  \
+  ((!LINE || __LINE__ == LINE) ? __builtin_object_size (x, 0) : __SIZE_MAX__)
+
+#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 __attribute__ ((noclone, noinline))
+sink (void *p)
+{
+  __builtin_free (p);
+}
+
+/* Identity function to require optimization to figure out the value
+   of the operand.  */
+int i (int x) { return x; }
+const char* s (const char *str) { return str; }
+
+void test_sprintf_chk_integer_value (void)
+{
+  T ( 1, "%i",  i (    0));         /* { dg-warning "nul just past the end" } */
+  T ( 1, "%i",  i (    1));         /* { dg-warning "nul just past the end" } */
+  T ( 1, "%i",  i (   -1));         /* { dg-warning "into a region" } */
+  T ( 1, "%i_", i (    1));         /* { dg-warning "character ._. at offset .2. just 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 just past the end" } */
+  T ( 1, "%u",  i (    0));         /* { dg-warning "nul just past the end" } */
+  T ( 1, "%x",  i (    0));         /* { dg-warning "nul just past the end" } */
+  T ( 1, "%#x", i (    0));         /* { dg-warning "nul just past the end" } */
+  T ( 1, "%x",  i (    1));         /* { dg-warning "nul just 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 just past the end" } */
+  T ( 2, "%i",  i (   10));         /* { dg-warning "nul just past the end" } */
+  T ( 2, "%i_", i (    0));         /* { dg-warning "nul just past the end" } */
+  T ( 2, "_%i", i (    0));         /* { dg-warning "nul just past the end" } */
+  T ( 2, "_%i_",i (    0));         /* { dg-warning "character ._. at offset .3. just past the end" } */
+  T ( 2, "%o",  i (    1));
+  T ( 2, "%o",  i (    7));
+  T ( 2, "%o",  i (  010));         /* { dg-warning "nul just 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 just past the end" } */
+  T ( 2, "%x",  i ( 0xff));         /* { dg-warning "nul just 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 just past the end" } */
+
+  T ( 8, "%8u", i (    1));         /* { dg-warning "nul just 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 just 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 a region of size .2." } */
+  T ( 2, "%i",  R (9, 10));     /* { dg-warning "may write a terminating nul past the end of a region of size .2." } */
+
+  T ( 3, "%i",  R (-9,   9));
+  T ( 3, "%i",  R ( 0,  99));
+  T ( 3, "%i",  R ( 0, 100));   /* { dg-warning "may write a terminating nul past the end of a region of size .3." } */
+
+  /* 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 just 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 + __LINE__, 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 just 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 a region of size .2." } */
+
+  T ( 3, "%i",  Ra (0,  99));
+  T ( 3, "%i",  Ra (0, 100));  /* { dg-warning "may write a terminating nul past the end of a region of size .3." } */
+}
+
+void test_sprintf_chk_range_sshort (signed short *a, signed short *b)
+{
+#undef Ra
+#define Ra(min, max) *range_sshort (a + __LINE__, 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 just past the end" } */
+  T ( 1, "%i",  Ra ( 0, 9));     /* { dg-warning "nul just 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 a region of size .2." } */
+  T ( 2, "%i",  Ra ( 9, 10));    /* { dg-warning "may write a terminating nul past the end of a region of size .2." } */
+
+  T ( 3, "%i",  Ra ( 0, 99));
+  T ( 3, "%i",  Ra (99, 999));   /* { dg-warning "may write a terminating nul past the end of a region of size .3." } */
+
+  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 a region of size .4." } */
+}