diff mbox

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

Message ID 57BCC6A4.3050004@gmail.com
State New
Headers show

Commit Message

Martin Sebor Aug. 23, 2016, 9:56 p.m. UTC
Attached is the latest patch.

The most important changes from the previous revision are:

  *  Implemented Joseph's suggestion to format floating point values
     in both down and up rounding modes and use the range of output
     bytes.

  *  Removed fixes to suppress the warning for GCC code.  These have
     already been approved and I'll commit them shortly.

  *  Incorporated David Malcolm's latest location range functions
     from c-format.c.

This patch still contains the workaround for the limitation of
the compute_builtin_object_size function (PR71831).  The separate
patch for it is in the review queue but hasn't been approved yet.
If/when it's approved I'll remove the workaround from this patch.

The patch also has a workaround for PR77336 (#pragma GCC diagnostic)
that I ran into with the recent changes.  I just submitted a patch
for that bug today so once it's approved I'll remove the #pragma.

The sprintf return value optimization is still disabled in this patch.
I'd like to get the code approved and committed without it first and
gain some more experience with the warning in the field while I do
more extensive testing of the optimization.

Here's a list of some of the next steps I'm considering besides
what I mentioned above:

  *  Add a new attribute (say destination_size) and enable the pass
     for user-defined functions decorated with it and with attribute
     format.

  *  Add support for scanf (PR72783 - Fortify scanf %s, %[ conversion
     specifiers).

  *  Improve/enhance compute_builtin_object_size (or add a new
     function) to more accurately detect sizes of character arrays.

  *  Improve/enhance c_strlen to detect the length of more constant
     strings.

Martin

Comments

Joseph Myers Aug. 23, 2016, 11 p.m. UTC | #1
Some observations:

* Does -fprintf-return-value allow for the possibility of snprintf failing 
because of a memory allocation failure and so returning -1 when GCC 
computed bounds on what it could return if successful?

* It looks like you convert to (signed/unsigned) char for %hh formats, 
etc.  Now, there is the possibility that the value passed was actually of 
type int, and out of range for those types.  And there is the possibility 
that the implementation might not itself convert those values to char / 
short (glibc didn't until 2006) - passing a value outside the range of the 
relevant type seems likely undefined behavior, so implementations may not 
actually need to convert, and there's an open question about whether the 
value actually needs to have been promoted from char/short in the caller 
(see my <https://www.polyomino.org.uk/computer/c/pre-dr-6a.txt>).  I don't 
know if you wish to allow at all for this issue.
Manuel López-Ibáñez Aug. 23, 2016, 11:41 p.m. UTC | #2
On 23 August 2016 at 22:56, Martin Sebor <msebor@gmail.com> wrote:
> Attached is the latest patch.

Some suggestions:

--Wno-format-contains-nul -Wno-format-extra-args -Wformat-nonliteral @gol
+-Wno-format-contains-nul -Wno-format-extra-args -Wformat-length=1 @gol

Most options that take levels are documented as:

-Wshift-overflow -Wshift-overflow=@var{n}
-Wstrict-overflow -Wstrict-overflow=@var{n}


+@item -Wformat-length
+@itemx -Wformat-length=@var{level}
+@opindex Wformat-length
+@opindex Wno-format-length
+@opindex ffreestanding
+@opindex fno-builtin
+@opindex Wformat-length=

This whole hunk is duplicated

Usually, at the end of the option description, it is mentioned which
options enabled this one (-Wformat=1 in this case, which is in turn
enabled by -Wall).

+The @option{-Wformat-length} option causes GCC to attempt to detect calls
+to formatted functions such as @code{sprintf} that might overflow the
+destination buffer, or bounded functions like @code{snprintf} that result
+in output truncation.

This text is a bit too verbose and quite different from other warning
options. A humble proposal:

Warn about calls to formatted functions, such as @code{sprintf}, that
may overflow the destination buffer, or bounded functions, such as
@code{snprintf} , that may? 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.

This is saying basically the same as the first sentence. Remove it?

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

When the exact number of bytes written by a format string directive
cannot be determined at compile-time, it is estimated based on
heuristics that depend on optimization and the @var{level} argument.
(I swapped the two because level is the next thing explained).

+The default setting of @var{level} is 1.  Level
+@var{1} employs a conservative approach that warns only about calls that
+most likely overflow the buffer or result in output truncation.  At this
+level, numeric arguments to format directives whose values are unknown
+are assumed to have the value of one, and strings of unknown length are
+assumed to have a length of zero.  Numeric arguments that are known to
+be bounded to a subrange of their type, or string arguments whose output
+is bounded by their directive's precision, are assumed to take on the value
+within the range that results in the most bytes on output.  Level @var{2}
+warns also bout calls that may overflow the destination buffer or result
+in truncation given an argument of sufficient length or magnitude.  At
+this level, unknown numeric arguments are assumed to have the minimum
+representable value for signed types with a precision greater than 1,
+and the maximum representable value otherwise.  Unknown string arguments
+are assumed to be 1 character long.

A better format (and it enables searching for specific levels) would be:
@table @gcctabopt
@item -Wformat-length=1
This is the warning level enabled by @option{-Wformat-length} and is enabled
by @option{-Wformat=1}, which is in turn enabled by @option{-Wall}. It
employs a conservative heuristic that warns only about calls that
most likely overflow the buffer or result in output truncation.  At this
level, numeric arguments to format directives whose values are unknown
are assumed to have the value of one, and strings of unknown length are
assumed to have a length of zero.  Numeric arguments that are known to
be bounded to a subrange of their type, or string arguments whose output
is bounded by their directive's precision, are assumed to take on the value
within the range that results in the most bytes on output.

@item -Wformat-length=2
This level also warns about calls that may overflow the destination
buffer or result
in truncation given an argument of sufficient length or magnitude.
Unknown numeric arguments are assumed to have the minimum
representable value for signed types with a precision greater than one,
and the maximum representable value otherwise.  Unknown string arguments
are assumed to be one character long.
@end table

+Enabling optimization will in most
+cases improve the accuracy of the warning, although in some cases it may
+also result in false positives.

Since the example is not about optimization. Move this paragraph after
the examples.

+For example, at level @var{1}, the call to @code{sprintf} below is diagnosed

For example, @option{-Wformat-length=1} warns about the call to
@code{sprintf} below

+At level @var{2}, the call
+is again diagnosed, but this time

@option{-Wformat-length=2} also warns about the call to
@code{sprintf}, but this time

+that the most closely corresponds to the @code{%p} format directive.

that most closely


+      if (!val)
+    {
+      if (fuzzy)
+        {
+          if (TREE_CODE (arg) == ADDR_EXPR)
+        return get_range_strlen (TREE_OPERAND (arg, 0), length,
+                     visited, type, fuzzy);
+
+          if (TREE_CODE (arg) == COMPONENT_REF
+          && TREE_CODE (TREE_TYPE (TREE_OPERAND (arg, 1))) == ARRAY_TYPE)
+        {
+          /* Use the type of the member array to determine the upper
+             bound on the length of the array.  This may be overly
+             optimistic if the array itself isn't NUL-terminated and
+             the caller relies on the subsequent member to contain
+             the NUL.  */
+          arg = TREE_OPERAND (arg, 1);
+          val = TYPE_SIZE_UNIT (TREE_TYPE (arg));
+          if (!val || integer_zerop (val))
+            return false;
+          val = fold_build2 (MINUS_EXPR, TREE_TYPE (val), val,
+                     integer_one_node);
+          /* Avoid using the array size as the minimum.  */
+          minlen = NULL;
+        }
+        }
+      else
+        return false;
+    }
+
       if (!val)
     return false;


You will save some horizontal space, save the else branch and improve
readability by doing:

if (!val && fuzzy)
{
}
if (!val)
 return false;

+/* Describes a location range outlining a substring within a string
+   literal.  */
+
+class substring_loc
+{
+ public:
+  substring_loc (location_t fmt_string_loc,
+         int caret_idx, int start_idx, int end_idx)
+    : m_fmt_string_loc (fmt_string_loc),
+    m_caret_idx (caret_idx), m_start_idx (start_idx), m_end_idx (end_idx)
+  { }
+
+  const char *get_location (location_t *out_loc) const;
+
+  location_t get_fmt_string_loc () const { return m_fmt_string_loc; }
+
+ private:
+  location_t m_fmt_string_loc;
+  int m_caret_idx;
+  int m_start_idx;
+  int m_end_idx;
+};
+
+/* The global record of string concatentations, for use in extracting
+   locations within string literals.  */
+
+GTY(()) string_concat_db *g_string_concat_db;
+
+/* Attempt to determine the source location of the substring.
+   If successful, return NULL and write the source location to *OUT_LOC.
+   Otherwise return an error message.  Error messages are intended
+   for GCC developers (to help debugging) rather than for end-users.  */
+
+const char *
+substring_loc::get_location (location_t *out_loc) const
+{
+  gcc_assert (out_loc);
+
+  if (!g_string_concat_db)
+    g_string_concat_db
+      = new (ggc_alloc <string_concat_db> ()) string_concat_db ();
+
+  static struct cpp_reader* parse_in;
+  if (!parse_in)
+    {
+      /* Create and initialize a preprocessing reader.  */
+      parse_in = cpp_create_reader (CLK_GNUC99, ident_hash, line_table);
+      cpp_init_iconv (parse_in);
+    }
+
+  return get_source_location_for_substring (parse_in, g_string_concat_db,
+                        m_fmt_string_loc, CPP_STRING,
+                        m_caret_idx, m_start_idx, m_end_idx,
+                        out_loc);
+}
+

Ideally, the diagnostics machinery would hide all these details from
the middle-end. Surely, most of this can be shared with c-format.c.

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

Should this go to hwint.h ?
Martin Sebor Aug. 24, 2016, 4:40 p.m. UTC | #3
On 08/23/2016 05:00 PM, Joseph Myers wrote:
> Some observations:
>
> * Does -fprintf-return-value allow for the possibility of snprintf failing
> because of a memory allocation failure and so returning -1 when GCC
> computed bounds on what it could return if successful?

No.  I recall having seen Glibc fail with ENOMEM years ago when
formatting a floating point number to a very large precision but
I haven't seen any implementation fail.  I haven't yet looked to
see if the Glibc failure can still happen.  My reading of C and
POSIX is that snprintf is only allowed to fail due to an encoding
error, not because it runs out of memory, so such a failure would
seem like a bug.

At the same time, the cause of the failure doesn't really matter.
If the function could fail, unless GCC could determine the failure
at compile time and avoid the optimization, the transformation
wouldn't be guaranteed to be safe.  It might be something to
consider and possibly accommodate in the implementation.  It's
one of the reasons why I want to expose the optimization to
more code before enabling it.

I also haven't yet thought about how to deal with it but if it
is a possibility we want to allow for then maybe a target hook
for libc implementers to set to indicate whether sprintf can fail
and when would work.  Libc implementations that can fail under
any conditions (whether allowed by the standard or not) would
need to disable (or not enable, depending on the default) the
optimization.  I'm certainly open to other ideas.

> * It looks like you convert to (signed/unsigned) char for %hh formats,
> etc.  Now, there is the possibility that the value passed was actually of
> type int, and out of range for those types.  And there is the possibility
> that the implementation might not itself convert those values to char /
> short (glibc didn't until 2006) - passing a value outside the range of the
> relevant type seems likely undefined behavior, so implementations may not
> actually need to convert, and there's an open question about whether the
> value actually needs to have been promoted from char/short in the caller
> (see my <https://www.polyomino.org.uk/computer/c/pre-dr-6a.txt>).  I don't
> know if you wish to allow at all for this issue.

It sounds like the concern is that for the following call (when
UCHAR_MAX is 255):

   sprintf (d, "%hhu", 1000)

some implementation (an old version of Glibc?) may have actually
produced four digits and returned 4 on the basis of C saying that
the %hhu argument must be an unsigned char (promoted to int) and
thus the behavior of the call being undefined.

I wouldn't think this to be the intended interpretation (C11 says
the argument "value shall be converted to signed char or unsigned
char before printing") but if it really was meant to be undefined
then having GCC treat the undefined call differently than libc
shouldn't be a problem.  That said, I've tried not to allow the
optimization for calls with undefined behavior so if there was
a serious concern about this causing trouble I could see about
detecting this case and disabling it.

Martin
Florian Weimer Aug. 24, 2016, 6:54 p.m. UTC | #4
On 08/24/2016 06:40 PM, Martin Sebor wrote:
> On 08/23/2016 05:00 PM, Joseph Myers wrote:
>> Some observations:
>>
>> * Does -fprintf-return-value allow for the possibility of snprintf
>> failing
>> because of a memory allocation failure and so returning -1 when GCC
>> computed bounds on what it could return if successful?
>
> No.  I recall having seen Glibc fail with ENOMEM years ago when
> formatting a floating point number to a very large precision but
> I haven't seen any implementation fail.  I haven't yet looked to
> see if the Glibc failure can still happen.  My reading of C and
> POSIX is that snprintf is only allowed to fail due to an encoding
> error, not because it runs out of memory, so such a failure would
> seem like a bug.

It can still happen, and not just for floating point.

#include <stdio.h>

int
main()
{
   printf("%0999999d\n", 5);
}

valgrind reports:

    total heap usage: 1 allocs, 1 frees, 1,000,031 bytes allocated

We are certainly not proud of this state of affairs, but it is unlikely 
to change anytime soon.

Florian
Martin Sebor Aug. 24, 2016, 7:18 p.m. UTC | #5
On 08/24/2016 12:54 PM, Florian Weimer wrote:
> On 08/24/2016 06:40 PM, Martin Sebor wrote:
>> On 08/23/2016 05:00 PM, Joseph Myers wrote:
>>> Some observations:
>>>
>>> * Does -fprintf-return-value allow for the possibility of snprintf
>>> failing
>>> because of a memory allocation failure and so returning -1 when GCC
>>> computed bounds on what it could return if successful?
>>
>> No.  I recall having seen Glibc fail with ENOMEM years ago when
>> formatting a floating point number to a very large precision but
>> I haven't seen any implementation fail.  I haven't yet looked to
>> see if the Glibc failure can still happen.  My reading of C and
>> POSIX is that snprintf is only allowed to fail due to an encoding
>> error, not because it runs out of memory, so such a failure would
>> seem like a bug.
>
> It can still happen, and not just for floating point.
>
> #include <stdio.h>
>
> int
> main()
> {
>    printf("%0999999d\n", 5);
> }
>
> valgrind reports:
>
>     total heap usage: 1 allocs, 1 frees, 1,000,031 bytes allocated

Thanks for the information (even though that's not the news I was
hoping for).  Is there a way to determine from the format string
whether the allocation will happen?  Does it just happen with
output beyond some length, for example?

Martin
Joseph Myers Aug. 24, 2016, 10:03 p.m. UTC | #6
On Wed, 24 Aug 2016, Martin Sebor wrote:

> No.  I recall having seen Glibc fail with ENOMEM years ago when
> formatting a floating point number to a very large precision but
> I haven't seen any implementation fail.  I haven't yet looked to
> see if the Glibc failure can still happen.  My reading of C and
> POSIX is that snprintf is only allowed to fail due to an encoding
> error, not because it runs out of memory, so such a failure would
> seem like a bug.

It's a general ISO C principle that there may be implementation limits, 
including in areas where no specific minimum limit is given in the 
standard, and even where there is a mimimum, that doesn't mean that every 
possible program that does not exceed that minimum does not exceed the 
limit in that area.  In this case, a specific minimum limit is given, 
7.21.6.1#15 "The number of characters that can be produced by any single 
conversion shall be at least 4095.".  That is, libc can't reject all cases 
of larger results, but it's possible that if memory is short then some 
cases with shorter results could still fail.

It's also a general POSIX principle that functions may have additional 
error conditions beyond those given in the standard, when it's possible 
for them to return errors at all.

(Since people may wish to use snprintf / dprintf in signal handlers, 
avoiding malloc where possible in those functions is still a good idea.)

> > * It looks like you convert to (signed/unsigned) char for %hh formats,
> > etc.  Now, there is the possibility that the value passed was actually of
> > type int, and out of range for those types.  And there is the possibility
> > that the implementation might not itself convert those values to char /
> > short (glibc didn't until 2006) - passing a value outside the range of the
> > relevant type seems likely undefined behavior, so implementations may not
> > actually need to convert, and there's an open question about whether the
> > value actually needs to have been promoted from char/short in the caller
> > (see my <https://www.polyomino.org.uk/computer/c/pre-dr-6a.txt>).  I don't
> > know if you wish to allow at all for this issue.
> 
> It sounds like the concern is that for the following call (when
> UCHAR_MAX is 255):
> 
>   sprintf (d, "%hhu", 1000)
> 
> some implementation (an old version of Glibc?) may have actually
> produced four digits and returned 4 on the basis of C saying that
> the %hhu argument must be an unsigned char (promoted to int) and
> thus the behavior of the call being undefined.

Yes.
Martin Sebor Aug. 24, 2016, 11:08 p.m. UTC | #7
On 08/24/2016 04:03 PM, Joseph Myers wrote:
> On Wed, 24 Aug 2016, Martin Sebor wrote:
>
>> No.  I recall having seen Glibc fail with ENOMEM years ago when
>> formatting a floating point number to a very large precision but
>> I haven't seen any implementation fail.  I haven't yet looked to
>> see if the Glibc failure can still happen.  My reading of C and
>> POSIX is that snprintf is only allowed to fail due to an encoding
>> error, not because it runs out of memory, so such a failure would
>> seem like a bug.
>
> It's a general ISO C principle that there may be implementation limits,
> including in areas where no specific minimum limit is given in the
> standard, and even where there is a mimimum, that doesn't mean that every
> possible program that does not exceed that minimum does not exceed the
> limit in that area.  In this case, a specific minimum limit is given,
> 7.21.6.1#15 "The number of characters that can be produced by any single
> conversion shall be at least 4095.".  That is, libc can't reject all cases
> of larger results, but it's possible that if memory is short then some
> cases with shorter results could still fail.

It sounds like using the 4095 limit should be safe even with Glibc.
I'll use it then, thanks.

>> It sounds like the concern is that for the following call (when
>> UCHAR_MAX is 255):
>>
>>    sprintf (d, "%hhu", 1000)
>>
>> some implementation (an old version of Glibc?) may have actually
>> produced four digits and returned 4 on the basis of C saying that
>> the %hhu argument must be an unsigned char (promoted to int) and
>> thus the behavior of the call being undefined.
>
> Yes.

Since it's presumably a Glibc bug (bug 2509?) is this something
you believe the optimization needs to worry about?  If so, can
you confirm that only Glibc versions 2.4 and prior are affected
and 2.5 is not?

Martin
Joseph Myers Aug. 25, 2016, 11:02 p.m. UTC | #8
On Wed, 24 Aug 2016, Martin Sebor wrote:

> Since it's presumably a Glibc bug (bug 2509?) is this something
> you believe the optimization needs to worry about?  If so, can
> you confirm that only Glibc versions 2.4 and prior are affected
> and 2.5 is not?

The code may not need to care about it, but the assumptions need to be 
made clear.  I haven't tried to confirm which glibc versions had an issue 
here.
Jeff Law Sept. 8, 2016, 7:21 p.m. UTC | #9
On 08/24/2016 10:40 AM, Martin Sebor wrote:
> On 08/23/2016 05:00 PM, Joseph Myers wrote:
>> Some observations:
>>
>> * Does -fprintf-return-value allow for the possibility of snprintf
>> failing
>> because of a memory allocation failure and so returning -1 when GCC
>> computed bounds on what it could return if successful?
>
> No.  I recall having seen Glibc fail with ENOMEM years ago when
> formatting a floating point number to a very large precision but
> I haven't seen any implementation fail.  I haven't yet looked to
> see if the Glibc failure can still happen.  My reading of C and
> POSIX is that snprintf is only allowed to fail due to an encoding
> error, not because it runs out of memory, so such a failure would
> seem like a bug.
>
> At the same time, the cause of the failure doesn't really matter.
> If the function could fail, unless GCC could determine the failure
> at compile time and avoid the optimization, the transformation
> wouldn't be guaranteed to be safe.  It might be something to
> consider and possibly accommodate in the implementation.  It's
> one of the reasons why I want to expose the optimization to
> more code before enabling it.
>
> I also haven't yet thought about how to deal with it but if it
> is a possibility we want to allow for then maybe a target hook
> for libc implementers to set to indicate whether sprintf can fail
> and when would work.  Libc implementations that can fail under
> any conditions (whether allowed by the standard or not) would
> need to disable (or not enable, depending on the default) the
> optimization.  I'm certainly open to other ideas.
So what are the implications for the optimization part of this patch?

Jeff
Martin Sebor Sept. 8, 2016, 7:31 p.m. UTC | #10
On 09/08/2016 01:21 PM, Jeff Law wrote:
> On 08/24/2016 10:40 AM, Martin Sebor wrote:
>> On 08/23/2016 05:00 PM, Joseph Myers wrote:
>>> Some observations:
>>>
>>> * Does -fprintf-return-value allow for the possibility of snprintf
>>> failing
>>> because of a memory allocation failure and so returning -1 when GCC
>>> computed bounds on what it could return if successful?
>>
>> No.  I recall having seen Glibc fail with ENOMEM years ago when
>> formatting a floating point number to a very large precision but
>> I haven't seen any implementation fail.  I haven't yet looked to
>> see if the Glibc failure can still happen.  My reading of C and
>> POSIX is that snprintf is only allowed to fail due to an encoding
>> error, not because it runs out of memory, so such a failure would
>> seem like a bug.
>>
>> At the same time, the cause of the failure doesn't really matter.
>> If the function could fail, unless GCC could determine the failure
>> at compile time and avoid the optimization, the transformation
>> wouldn't be guaranteed to be safe.  It might be something to
>> consider and possibly accommodate in the implementation.  It's
>> one of the reasons why I want to expose the optimization to
>> more code before enabling it.
>>
>> I also haven't yet thought about how to deal with it but if it
>> is a possibility we want to allow for then maybe a target hook
>> for libc implementers to set to indicate whether sprintf can fail
>> and when would work.  Libc implementations that can fail under
>> any conditions (whether allowed by the standard or not) would
>> need to disable (or not enable, depending on the default) the
>> optimization.  I'm certainly open to other ideas.
> So what are the implications for the optimization part of this patch?

By coincidence I've just posted an updated patch that handles this
situation by avoiding the optimization (and issuing a warning pointing
out that a directive produced more that 4,095 bytes worth of output).
Ditto for INT_MAX.

Martin
diff mbox

Patch

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

gcc/ChangeLog:
	PR middle-end/49905
	* Makefile.in (OBJS): Add gimple-ssa-sprintf.o.
	* config/linux.h (TARGET_LIBC_PRINTF_POINTER_FORMAT): Redefine.
	* config/sol2.h (TARGET_LIBC_PRINTF_POINTER_FORMAT): Same.
	* doc/invoke.texi (-Wformat-length, -fprintf-return-value): New
	options.
	* doc/tm.texi.in (TARGET_LIBC_PRINTF_POINTER_FORMAT): Document.
	* doc/tm.texi: Regenerate.
	* gimple-fold.h (get_range_strlen): New function.
	(get_maxval_strlen): Declare existing function.
	* gimple-fold.c (get_range_strlen): Add arguments and compute both
	maximum and minimum.
	 (get_range_strlen): Define overload.
	(get_maxval_strlen): Adjust.
	* gimple-ssa-sprintf.c: New file and pass.
	* passes.def (pass_sprintf_length): Add new pass.
	* targhooks.h (default_libc_printf_round_mode): Declare new function.
	(default_libc_printf_pointer_format): Same.
	(gnu_libc_printf_pointer_format): Same.
	(solaris_libc_printf_pointer_format): Same.
	* targhooks.c (default_libc_printf_round_mode): Define new function.
	(default_libc_printf_pointer_format): Same.
	(gnu_libc_printf_pointer_format): Same.
	(solaris_libc_printf_pointer_format): Same.

gcc/c-family/ChangeLog:
	PR middle-end/49905
	* c.opt: Add -Wformat-length and -fprintf-return-value.

gcc/testsuite/ChangeLog:
	PR middle-end/49905
	* gcc.dg/tree-ssa/builtin-sprintf-warn-1.c: New test.
	* gcc.dg/tree-ssa/builtin-sprintf-warn-2.c: New test.
	* gcc.dg/tree-ssa/builtin-sprintf-warn-3.c: New test.
	* gcc.dg/tree-ssa/builtin-sprintf.c: New test.

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 7a0160f..4f30454 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1296,6 +1296,7 @@  OBJS = \
 	gimple-ssa-nonnull-compare.o \
 	gimple-ssa-split-paths.o \
 	gimple-ssa-strength-reduction.o \
+	gimple-ssa-sprintf.o \
 	gimple-streamer-in.o \
 	gimple-streamer-out.o \
 	gimple-walk.o \
diff --git a/gcc/c-family/c-ada-spec.c b/gcc/c-family/c-ada-spec.c
index a4e0c38..6a8e04b 100644
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index a5358ed..287eb55 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.  Same as -Wformat-length=1.
+
 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 write past the end
+of the destination region.
+
 Wignored-qualifiers
 C C++ Var(warn_ignored_qualifiers) Warning EnabledBy(Wextra)
 Warn whenever type qualifiers are ignored.
@@ -1455,6 +1465,10 @@  fpretty-templates
 C++ ObjC++ Var(flag_pretty_templates) Init(1)
 -fno-pretty-templates Do not pretty-print template specializations as the template signature followed by the arguments.
 
+fprintf-return-value
+C ObjC C++ ObjC++ LTO Optimization Var(flag_printf_return_value) Init(0)
+Treat known sprintf return values as constants.
+
 freplace-objc-classes
 ObjC ObjC++ LTO Var(flag_replace_objc_classes)
 Used in Fix-and-Continue mode to indicate that object files may be swapped in at runtime.
diff --git a/gcc/config/linux.h b/gcc/config/linux.h
index 9aeeb94..2320c8f 100644
--- a/gcc/config/linux.h
+++ b/gcc/config/linux.h
@@ -208,3 +208,7 @@  see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
 # define TARGET_LIBC_HAS_FUNCTION linux_libc_has_function
 
 #endif
+
+/* The format string to which "%p" corresponds.  */
+#undef TARGET_LIBC_PRINTF_POINTER_FORMAT
+#define TARGET_LIBC_PRINTF_POINTER_FORMAT gnu_libc_printf_pointer_format
diff --git a/gcc/config/sol2.h b/gcc/config/sol2.h
index 50f2b38..6f02708 100644
--- a/gcc/config/sol2.h
+++ b/gcc/config/sol2.h
@@ -440,6 +440,10 @@  along with GCC; see the file COPYING3.  If not see
 #undef TARGET_LIBC_HAS_FUNCTION
 #define TARGET_LIBC_HAS_FUNCTION default_libc_has_function
 
+/* The format string to which "%p" corresponds.  */
+#undef TARGET_LIBC_PRINTF_POINTER_FORMAT
+#define TARGET_LIBC_PRINTF_POINTER_FORMAT solaris_libc_printf_pointer_format
+
 extern GTY(()) tree solaris_pending_aligns;
 extern GTY(()) tree solaris_pending_inits;
 extern GTY(()) tree solaris_pending_finis;
diff --git a/gcc/cp/mangle.c b/gcc/cp/mangle.c
index d8b5c45..5859d62 100644
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index d04be6f..284b9d8 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -268,7 +268,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=1 @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
@@ -379,7 +380,7 @@  Objective-C and Objective-C++ Dialects}.
 -fno-toplevel-reorder -fno-trapping-math -fno-zero-initialized-in-bss @gol
 -fomit-frame-pointer -foptimize-sibling-calls @gol
 -fpartial-inlining -fpeel-loops -fpredictive-commoning @gol
--fprefetch-loop-arrays @gol
+-fprefetch-loop-arrays -fprintf-return-value @gol
 -fprofile-correction @gol
 -fprofile-use -fprofile-use=@var{path} -fprofile-values @gol
 -fprofile-reorder-functions @gol
@@ -3825,6 +3826,155 @@  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 formatted functions such as @code{sprintf} that might overflow the
+destination buffer, or bounded functions like @code{snprintf} that result
+in output truncation.  GCC counts the number of bytes that each format
+string and directive within it writes into the provided buffer and, when
+it detects that more bytes that fit in the destination buffer may be output,
+it emits a warning.  Directives whose arguments have values that can be
+determined at compile-time account for the exact number of bytes they write.
+Directives with arguments whose values cannot be determined are processed
+based on heuristics that depend on the @var{level} argument to the option,
+and on optimization.  The default setting of @var{level} is 1.  Level
+@var{1} employs a conservative approach that warns only about calls that
+most likely overflow the buffer or result in output truncation.  At this
+level, numeric arguments to format directives whose values are unknown
+are assumed to have the value of one, and strings of unknown length are
+assumed to have a length of zero.  Numeric arguments that are known to
+be bounded to a subrange of their type, or string arguments whose output
+is bounded by their directive's precision, are assumed to take on the value
+within the range that results in the most bytes on output.  Level @var{2}
+warns also bout calls that may overflow the destination buffer or result
+in truncation given an argument of sufficient length or magnitude.  At
+this level, unknown numeric arguments are assumed to have the minimum
+representable value for signed types with a precision greater than 1,
+and the maximum representable value otherwise.  Unknown string arguments
+are assumed to be 1 character long.  Enabling optimization will in most
+cases improve the accuracy of the warning, although in some cases it may
+also result in false positives.
+
+For example, at level @var{1}, the call to @code{sprintf} below is diagnosed
+because even with both @var{a} and @var{b} equal to zero, the terminating
+NUL character (@code{'\0'}) appended by the function to the destination
+buffer will be written past its end.  Increasing the size of the buffer by
+a single byte is sufficient to avoid the warning.  At level @var{2}, the call
+is again diagnosed, but this time because with @var{a} equal to a 32-bit
+@code{INT_MIN} the first @code{%i} directive will write some of its digits
+beyond the end of the destination buffer.  To make the call safe regardless
+of the values of the two variables the size of the destination buffer must
+be increased to at least 34 bytes.  GCC includes the minimum size of the
+buffer in an inforational note following the warning.
+
+@smallexample
+void f (int a, int b)
+@{
+  char buf [12];
+  sprintf (buf, "a = %i, b = %i\n", a, b);
+@}
+@end smallexample
+
+An alternative to increasing the size of the destination buffer is to
+constrain the range of formatted values.  The maximum length of string
+arguments can be bounded by specifying the precision in the fortmat
+directive.  When numeric arguments of format directives can be assumed
+to be bounded by less than the precision of their type, choosing
+an appropriate length modifier to the format character will reduce
+the minimum buffer size.  For exampe, if @var{a} and @var{b} above can
+be assumed to be within the precision of the @code{short int} type then
+using either the @code{%hi} format directive or casting the argument to
+@code{short} reduces the maximum required size of the buffer to 24 bytes.
+
+@smallexample
+void f (int a, int b)
+@{
+  char buf [23];
+  sprintf (buf, "a = %hi, b = %i\n", a, (short)b);
+@}
+@end smallexample
+
+@item -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 formatted 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
@@ -7765,6 +7915,30 @@  dependent on the structure of loops within the source code.
 
 Disabled at level @option{-Os}.
 
+@item -fprintf-return-value
+@opindex fprintf-return-value
+Substitute constants for known return values of formatted output functions
+such as @code{sprintf}, @code{snprintf}, @code{vsprintf}, and @code{vsnprintf}
+(but not @code{printf} of @code{fprintf}.  This optimization makes it possible
+to optimize or even eliminate branches based on the known return value of
+these functions called with arguments that are either constant, or whose
+values are known to be in a range that makes determining the exact return
+value possible.  For example, both the branch and the body of the @code{if}
+statement (but not the call to @code{snprint}) can be optimized away when
+@code{i} is a 32-bit or smaller integer because the return value is guaranteed
+to be at most 8.
+
+@smallexample
+char buf[9];
+if (snprintf (buf, "%08x", i) >= sizeof buf)
+  @dots{}
+@end smallexample
+
+The @option{-fprintf-return-value} option relies on other optimizations
+and yields best results with @option{-O2}.  It works in tandem with the
+@option{-Wformat-length} option.  The @option{-fprintf-return-value}
+option is disabled by default.
+
 @item -fno-peephole
 @itemx -fno-peephole2
 @opindex fno-peephole
diff --git a/gcc/doc/tm.texi b/gcc/doc/tm.texi
index 9edb006..ba309d8 100644
--- a/gcc/doc/tm.texi
+++ b/gcc/doc/tm.texi
@@ -5331,6 +5331,13 @@  In either case, it remains possible to select code-generation for the alternate
 scheme, by means of compiler command line switches.
 @end defmac
 
+@deftypefn {Target Hook} {const char *} TARGET_LIBC_PRINTF_POINTER_FORMAT (tree, const char **@var{flags})
+A hook to determine the target @code{printf} implementation format string
+that the most closely corresponds to the @code{%p} format directive.
+The object pointed to by the @var{flags} is set to a string consisting
+of recognized format flags such as the @code{'#'} character.
+@end deftypefn
+
 @node Addressing Modes
 @section Addressing Modes
 @cindex addressing modes
diff --git a/gcc/doc/tm.texi.in b/gcc/doc/tm.texi.in
index a72c3d8..a70ed89 100644
--- a/gcc/doc/tm.texi.in
+++ b/gcc/doc/tm.texi.in
@@ -4079,6 +4079,13 @@  In either case, it remains possible to select code-generation for the alternate
 scheme, by means of compiler command line switches.
 @end defmac
 
+@deftypefn {Target Hook} {const char *} TARGET_LIBC_PRINTF_POINTER_FORMAT (tree, const char **@var{flags})
+A hook to determine the target @code{printf} implementation format string
+that the most closely corresponds to the @code{%p} format directive.
+The object pointed to by the @var{flags} is set to a string consisting
+of recognized format flags such as the @code{'#'} character.
+@end deftypefn
+
 @node Addressing Modes
 @section Addressing Modes
 @cindex addressing modes
diff --git a/gcc/gimple-fold.c b/gcc/gimple-fold.c
index fbbe520..9685ef8 100644
--- a/gcc/gimple-fold.c
+++ b/gcc/gimple-fold.c
@@ -1159,21 +1159,30 @@  gimple_fold_builtin_memset (gimple_stmt_iterator *gsi, tree c, tree len)
 }
 
 
-/* Return the string length, maximum string length or maximum value of
-   ARG in LENGTH.
-   If ARG is an SSA name variable, follow its use-def chains.  If LENGTH
-   is not NULL and, for TYPE == 0, its value is not equal to the length
-   we determine or if we are unable to determine the length or value,
-   return false.  VISITED is a bitmap of visited variables.
-   TYPE is 0 if string length should be returned, 1 for maximum string
-   length and 2 for maximum value ARG can have.  */
+/* Obtain the minimum and maximum string length or minimum and maximum
+   value of ARG in LENGTH[0] and LENGTH[1], respectively.
+   If ARG is an SSA name variable, follow its use-def chains.  When
+   TYPE == 0, if LENGTH[1] is not equal to the length we determine or
+   if we are unable to determine the length or value, return False.
+   VISITED is a bitmap of visited variables.
+   TYPE is 0 if string length should be obtained, 1 for maximum string
+   length and 2 for maximum value ARG can have.
+   When FUZZY is set and the length of a string cannot be determined,
+   the function instead considers as the maximum possible length the
+   size of a character array it may refer to.  */
 
 static bool
-get_maxval_strlen (tree arg, tree *length, bitmap *visited, int type)
+get_range_strlen (tree arg, tree length[2], bitmap *visited, int type,
+		  bool fuzzy)
 {
   tree var, val;
   gimple *def_stmt;
 
+  /* The minimum and maximum length.  The MAXLEN pointer stays unchanged
+     but MINLEN may be cleared during the execution of the function.  */
+  tree *minlen = length;
+  tree* const maxlen = length + 1;
+
   if (TREE_CODE (arg) != SSA_NAME)
     {
       /* We can end up with &(*iftmp_1)[0] here as well, so handle it.  */
@@ -1184,8 +1193,8 @@  get_maxval_strlen (tree arg, tree *length, bitmap *visited, int type)
 	  tree aop0 = TREE_OPERAND (TREE_OPERAND (arg, 0), 0);
 	  if (TREE_CODE (aop0) == INDIRECT_REF
 	      && TREE_CODE (TREE_OPERAND (aop0, 0)) == SSA_NAME)
-	    return get_maxval_strlen (TREE_OPERAND (aop0, 0),
-				      length, visited, type);
+	    return get_range_strlen (TREE_OPERAND (aop0, 0),
+				     length, visited, type, fuzzy);
 	}
 
       if (type == 2)
@@ -1197,26 +1206,65 @@  get_maxval_strlen (tree arg, tree *length, bitmap *visited, int type)
 	}
       else
 	val = c_strlen (arg, 1);
+
+      if (!val)
+	{
+	  if (fuzzy)
+	    {
+	      if (TREE_CODE (arg) == ADDR_EXPR)
+		return get_range_strlen (TREE_OPERAND (arg, 0), length,
+					 visited, type, fuzzy);
+
+	      if (TREE_CODE (arg) == COMPONENT_REF
+		  && TREE_CODE (TREE_TYPE (TREE_OPERAND (arg, 1))) == ARRAY_TYPE)
+		{
+		  /* Use the type of the member array to determine the upper
+		     bound on the length of the array.  This may be overly
+		     optimistic if the array itself isn't NUL-terminated and
+		     the caller relies on the subsequent member to contain
+		     the NUL.  */
+		  arg = TREE_OPERAND (arg, 1);
+		  val = TYPE_SIZE_UNIT (TREE_TYPE (arg));
+		  if (!val || integer_zerop (val))
+		    return false;
+		  val = fold_build2 (MINUS_EXPR, TREE_TYPE (val), val,
+				     integer_one_node);
+		  /* Avoid using the array size as the minimum.  */
+		  minlen = NULL;
+		}
+	    }
+	  else
+	    return false;
+	}
+
       if (!val)
 	return false;
 
-      if (*length)
+      if (minlen
+	  && (!*minlen
+	      || (type > 0
+		  && TREE_CODE (*minlen) == INTEGER_CST
+		  && TREE_CODE (val) == INTEGER_CST
+		  && tree_int_cst_lt (val, *minlen))))
+	*minlen = val;
+
+      if (*maxlen)
 	{
 	  if (type > 0)
 	    {
-	      if (TREE_CODE (*length) != INTEGER_CST
+	      if (TREE_CODE (*maxlen) != INTEGER_CST
 		  || TREE_CODE (val) != INTEGER_CST)
 		return false;
 
-	      if (tree_int_cst_lt (*length, val))
-		*length = val;
+	      if (tree_int_cst_lt (*maxlen, val))
+		*maxlen = val;
 	      return true;
 	    }
-	  else if (simple_cst_equal (val, *length) != 1)
+	  else if (simple_cst_equal (val, *maxlen) != 1)
 	    return false;
 	}
 
-      *length = val;
+      *maxlen = val;
       return true;
     }
 
@@ -1244,14 +1292,14 @@  get_maxval_strlen (tree arg, tree *length, bitmap *visited, int type)
             || gimple_assign_unary_nop_p (def_stmt))
           {
             tree rhs = gimple_assign_rhs1 (def_stmt);
-            return get_maxval_strlen (rhs, length, visited, type);
+            return get_range_strlen (rhs, length, visited, type, fuzzy);
           }
 	else if (gimple_assign_rhs_code (def_stmt) == COND_EXPR)
 	  {
 	    tree op2 = gimple_assign_rhs2 (def_stmt);
 	    tree op3 = gimple_assign_rhs3 (def_stmt);
-	    return get_maxval_strlen (op2, length, visited, type)
-		   && get_maxval_strlen (op3, length, visited, type);
+	    return get_range_strlen (op2, length, visited, type, fuzzy)
+	      && get_range_strlen (op3, length, visited, type, fuzzy);
           }
         return false;
 
@@ -1274,7 +1322,8 @@  get_maxval_strlen (tree arg, tree *length, bitmap *visited, int type)
             if (arg == gimple_phi_result (def_stmt))
               continue;
 
-            if (!get_maxval_strlen (arg, length, visited, type))
+            if (!get_range_strlen (arg, length, visited, type, fuzzy)
+		&& !fuzzy)
               return false;
           }
         }
@@ -1285,17 +1334,39 @@  get_maxval_strlen (tree arg, tree *length, bitmap *visited, int type)
     }
 }
 
+/* Determine the minimum and maximum value or string length that ARG
+   refers to and store each in the first two elements of MINMAXLEN.
+   For expressions that point to strings of unknown lengths that are
+   character arrays use the upper bound of the array as the maximum
+   lenght.  For example, given an expression like 'x ? array : "xyz"'
+   and array being declared as char array[8], MINMAXLEN[0] will be
+   set to 3 and MINMAXLEN[1] to 7, the longest string that could be
+   stored in array.  */
+
+void get_range_strlen (tree arg, tree minmaxlen[2])
+{
+  bitmap visited = NULL;
+
+  minmaxlen[0] = NULL_TREE;
+  minmaxlen[1] = NULL_TREE;
+
+  get_range_strlen (arg, minmaxlen, &visited, 1, true);
+
+  if (visited)
+    BITMAP_FREE (visited);
+}
+
 tree
 get_maxval_strlen (tree arg, int type)
 {
   bitmap visited = NULL;
-  tree len = NULL_TREE;
-  if (!get_maxval_strlen (arg, &len, &visited, type))
-    len = NULL_TREE;
+  tree len[2] = { NULL_TREE, NULL_TREE };
+  if (!get_range_strlen (arg, len, &visited, type, false))
+    len[1] = NULL_TREE;
   if (visited)
     BITMAP_FREE (visited);
 
-  return len;
+  return len[1];
 }
 
 
diff --git a/gcc/gimple-fold.h b/gcc/gimple-fold.h
index f314714..5add30c 100644
--- a/gcc/gimple-fold.h
+++ b/gcc/gimple-fold.h
@@ -24,6 +24,8 @@  along with GCC; see the file COPYING3.  If not see
 
 extern tree canonicalize_constructor_val (tree, tree);
 extern tree get_symbol_constant_value (tree);
+extern void get_range_strlen (tree, tree[2]);
+extern tree get_maxval_strlen (tree, int);
 extern void gimplify_and_update_call_from_tree (gimple_stmt_iterator *, tree);
 extern bool fold_stmt (gimple_stmt_iterator *);
 extern bool fold_stmt (gimple_stmt_iterator *, tree (*) (tree));
diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c
new file mode 100644
index 0000000..93cd3b7
--- /dev/null
+++ b/gcc/gimple-ssa-sprintf.c
@@ -0,0 +1,2615 @@ 
+/* Copyright (C) 2016 Free Software Foundation, Inc.
+   Contributed by Martin Sebor <msebor@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+/* This file implements the printf-return-value pass.  The pass does
+   two things: 1) it analyzes calls to formatted output functions like
+   sprintf looking for possible buffer overflows and calls to bounded
+   functions like snprintf for early truncation (and under the control
+   of the -Wformat-length option, issues warnings), and 2) under the
+   control of the -fprintf-return-value option it folds the return
+   value of safe calls into constants, making it possible to eliminate
+   code that depends on the value of those constants.
+
+   For all functions (bounded or not) the pass uses the size of the
+   destination object.  That means that it will diagnose calls to
+   snprintf not on the basis of the size specified by the function's
+   second argument but rathger on the basis of the size the first
+   argument points to (if possible).  For bounds-checking built-ins
+   like __builtin___snprintf_chk the pass uses the size typically
+   determined by __builtin_object_size and passed to the built-in
+   by the Glibc inline wrapper.
+
+   The pass handles all forms of standard sprintf format directives,
+   including character, integer, floating point, pointer, and strings,
+   with the standard C flags, widths, and precisions.  For integers
+   and strings it computes the length of output itself.  For floating
+   point it uses MPFR to format known constants with up and down
+   rounding and uses the resulting range of output lengths.  For
+   strings it uses the length of string literals and the sizes of
+   character arrays that a character pointer may point to as a bound
+   on the longest string.  */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "backend.h"
+#include "tree.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "ssa.h"
+#include "gimple-fold.h"
+#include "gimple-pretty-print.h"
+#include "diagnostic-core.h"
+#include "fold-const.h"
+#include "gimple-iterator.h"
+#include "tree-ssa.h"
+#include "tree-object-size.h"
+#include "params.h"
+#include "tree-cfg.h"
+#include "calls.h"
+#include "cfgloop.h"
+#include "intl.h"
+
+#include "builtins.h"
+#include "stor-layout.h"
+
+#include "realmpfr.h"
+#include "target.h"
+#include "targhooks.h"
+
+#include "cpplib.h"
+#include "input.h"
+#include "toplev.h"
+#include "substring-locations.h"
+#include "diagnostic.h"
+
+#ifndef TARGET_LIBC_PRINTF_POINTER_FORMAT
+#  define TARGET_LIBC_PRINTF_POINTER_FORMAT   default_libc_printf_pointer_format
+#endif
+
+namespace {
+
+const pass_data pass_data_sprintf_length = {
+  GIMPLE_PASS,             // pass type
+  "printf-return-value",   // pass name
+  OPTGROUP_NONE,           // optinfo_flags
+  TV_NONE,                 // tv_id
+  PROP_cfg,                // properties_required
+  0,	                   // properties_provided
+  0,	                   // properties_destroyed
+  0,	                   // properties_start
+  0,	                   // properties_finish
+};
+
+struct format_result;
+
+class pass_sprintf_length : public gimple_opt_pass
+{
+  bool fold_return_value;
+
+public:
+  pass_sprintf_length (gcc::context *ctxt)
+    : gimple_opt_pass (pass_data_sprintf_length, ctxt),
+    fold_return_value (false)
+  { }
+
+  opt_pass * clone () { return new pass_sprintf_length (m_ctxt); }
+
+  virtual bool gate (function *);
+
+  virtual unsigned int execute (function *);
+
+  void set_pass_param (unsigned int n, bool param)
+    {
+      gcc_assert (n == 0);
+      fold_return_value = param;
+    }
+
+  void handle_gimple_call (gimple_stmt_iterator);
+
+  struct call_info;
+  void compute_format_length (const call_info &, format_result *);
+};
+
+bool
+pass_sprintf_length::gate (function *)
+{
+  /* Run the pass iff -Warn-format-length is specified and either
+     not optimizing and the pass is being invoked early, or when
+     optimizing and the pass is being invoked during optimization
+     (i.e., "late").  */
+  return ((0 < warn_format_length || flag_printf_return_value)
+	  && (0 < optimize) == fold_return_value);
+}
+
+/* The result of a call to a formatting function.  */
+
+struct format_result
+{
+  /* Number of characters written by the formatting function, exact,
+     minimum and maximum when an exact number cannot be determined.
+     Setting the minimum to HOST_WIDE_INT_MAX disables all length
+     tracking for the remainder of the format string.
+     Setting either of the other two members to HOST_WIDE_INT_MAX
+     disables the exact or maximum length tracking, respectively,
+     but continues to track the maximum.  */
+  unsigned HOST_WIDE_INT number_chars;
+  unsigned HOST_WIDE_INT number_chars_min;
+  unsigned HOST_WIDE_INT number_chars_max;
+
+  /* True when the range given by NUMBER_CHARS_MIN and NUMBER_CHARS_MAX
+     is the output of all directives determined to be bounded to some
+     subrange of their types or possible lengths, false otherwise.
+     Note that BOUNDED only implies that the length of a function's
+     output is known to be within some range, not that it's constant
+     and a candidate for folding.  */
+  bool bounded;
+
+  /* True when the output of the formatting call is constant (and
+     thus a candidate for string constant folding).  This is rare
+     and typically requires that the arguments of all directives
+     are also constant.  Constant implies bounded.  */
+  bool constant;
+
+  /* True when a floating point directive has been seen in the format
+     string.  */
+  bool floating;
+
+  /* True when an intermediate result has caused a warning.  Used to
+     avoid issuing duplicate warnings while finishing the processing
+     of a call.  */
+  bool warned;
+
+  /* Preincrement the number of output characters by 1.  */
+  format_result& operator++ ()
+  {
+    return *this += 1;
+  }
+
+  /* Postincrement the number of output characters by 1.  */
+  format_result operator++ (int)
+  {
+    format_result prev (*this);
+    *this += 1;
+    return prev;
+  }
+
+  /* Increment the number of output characters by N.  */
+  format_result& operator+= (unsigned HOST_WIDE_INT n)
+  {
+    gcc_assert (n < HOST_WIDE_INT_MAX);
+
+    if (number_chars < HOST_WIDE_INT_MAX)
+      number_chars += n;
+    if (number_chars_min < HOST_WIDE_INT_MAX)
+      number_chars_min += n;
+    if (number_chars_max < HOST_WIDE_INT_MAX)
+      number_chars_max += n;
+    return *this;
+  }
+};
+
+/* Return the constant initial value of DECL if available or DECL
+   otherwise.  Same as the synonymous function in c/c-typeck.c.  */
+
+static tree
+decl_constant_value (tree decl)
+{
+  if (/* Don't change a variable array bound or initial value to a constant
+	 in a place where a variable is invalid.  Note that DECL_INITIAL
+	 isn't valid for a PARM_DECL.  */
+      current_function_decl != 0
+      && TREE_CODE (decl) != PARM_DECL
+      && !TREE_THIS_VOLATILE (decl)
+      && TREE_READONLY (decl)
+      && DECL_INITIAL (decl) != 0
+      && TREE_CODE (DECL_INITIAL (decl)) != ERROR_MARK
+      /* This is invalid if initial value is not constant.
+	 If it has either a function call, a memory reference,
+	 or a variable, then re-evaluating it could give different results.  */
+      && TREE_CONSTANT (DECL_INITIAL (decl))
+      /* Check for cases where this is sub-optimal, even though valid.  */
+      && TREE_CODE (DECL_INITIAL (decl)) != CONSTRUCTOR)
+    return DECL_INITIAL (decl);
+  return decl;
+}
+
+/* Given FORMAT, set *PLOC to the source location of the format string
+   and return the format string if it is known or null otherwise.  */
+
+static const char*
+get_format_string (tree format, location_t *ploc)
+{
+  if (VAR_P (format))
+    {
+      /* Pull out a constant value if the front end didn't.  */
+      format = decl_constant_value (format);
+      STRIP_NOPS (format);
+    }
+
+  if (integer_zerop (format))
+    {
+      /* FIXME: Diagnose null format string if it hasn't been diagnosed
+	 by -Wformat (the latter diagnoses only nul pointer constants,
+	 this pass can do better).  */
+      return NULL;
+    }
+
+  HOST_WIDE_INT offset = 0;
+
+  if (TREE_CODE (format) == POINTER_PLUS_EXPR)
+    {
+      tree arg0 = TREE_OPERAND (format, 0);
+      tree arg1 = TREE_OPERAND (format, 1);
+      STRIP_NOPS (arg0);
+      STRIP_NOPS (arg1);
+
+      if (TREE_CODE (arg1) != INTEGER_CST)
+	return NULL;
+
+      format = arg0;
+
+      /* POINTER_PLUS_EXPR offsets are to be interpreted signed.  */
+      if (!cst_and_fits_in_hwi (arg1))
+	return NULL;
+
+      offset = int_cst_value (arg1);
+    }
+
+  if (TREE_CODE (format) != ADDR_EXPR)
+    return NULL;
+
+  *ploc = EXPR_LOC_OR_LOC (format, input_location);
+
+  format = TREE_OPERAND (format, 0);
+
+  if (TREE_CODE (format) == ARRAY_REF
+      && tree_fits_shwi_p (TREE_OPERAND (format, 1))
+      && (offset += tree_to_shwi (TREE_OPERAND (format, 1))) >= 0)
+    format = TREE_OPERAND (format, 0);
+
+  if (offset < 0)
+    return NULL;
+
+  tree array_init;
+  tree array_size = NULL_TREE;
+
+  if (VAR_P (format)
+      && TREE_CODE (TREE_TYPE (format)) == ARRAY_TYPE
+      && (array_init = decl_constant_value (format)) != format
+      && TREE_CODE (array_init) == STRING_CST)
+    {
+      /* Extract the string constant initializer.  Note that this may
+	 include a trailing NUL character that is not in the array (e.g.
+	 const char a[3] = "foo";).  */
+      array_size = DECL_SIZE_UNIT (format);
+      format = array_init;
+    }
+
+  if (TREE_CODE (format) != STRING_CST)
+    return NULL;
+
+  if (TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (format))) != char_type_node)
+    {
+      /* Wide format string.  */
+      return NULL;
+    }
+
+  const char *fmtstr = TREE_STRING_POINTER (format);
+  unsigned fmtlen = TREE_STRING_LENGTH (format);
+
+  if (array_size)
+    {
+      /* Variable length arrays can't be initialized.  */
+      gcc_assert (TREE_CODE (array_size) == INTEGER_CST);
+
+      if (tree_fits_shwi_p (array_size))
+	{
+	  HOST_WIDE_INT array_size_value = tree_to_shwi (array_size);
+	  if (array_size_value > 0
+	      && array_size_value == (int) array_size_value
+	      && fmtlen > array_size_value)
+	    fmtlen = array_size_value;
+	}
+    }
+  if (offset)
+    {
+      if (offset >= fmtlen)
+	return NULL;
+
+      fmtstr += offset;
+      fmtlen -= offset;
+    }
+
+  if (fmtlen < 1 || fmtstr[--fmtlen] != 0)
+    {
+      /* FIXME: Diagnose an unterminated format string if it hasn't been
+	 diagnosed by -Wformat.  Similarly to a null format pointer,
+	 -Wformay diagnoses only nul pointer constants, this pass can
+	 do better).  */
+      return NULL;
+    }
+
+  return fmtstr;
+}
+
+/* Describes a location range outlining a substring within a string
+   literal.  */
+
+class substring_loc
+{
+ public:
+  substring_loc (location_t fmt_string_loc,
+		 int caret_idx, int start_idx, int end_idx)
+    : m_fmt_string_loc (fmt_string_loc),
+    m_caret_idx (caret_idx), m_start_idx (start_idx), m_end_idx (end_idx)
+  { }
+
+  const char *get_location (location_t *out_loc) const;
+
+  location_t get_fmt_string_loc () const { return m_fmt_string_loc; }
+
+ private:
+  location_t m_fmt_string_loc;
+  int m_caret_idx;
+  int m_start_idx;
+  int m_end_idx;
+};
+
+/* The global record of string concatentations, for use in extracting
+   locations within string literals.  */
+
+GTY(()) string_concat_db *g_string_concat_db;
+
+/* Attempt to determine the source location of the substring.
+   If successful, return NULL and write the source location to *OUT_LOC.
+   Otherwise return an error message.  Error messages are intended
+   for GCC developers (to help debugging) rather than for end-users.  */
+
+const char *
+substring_loc::get_location (location_t *out_loc) const
+{
+  gcc_assert (out_loc);
+
+  if (!g_string_concat_db)
+    g_string_concat_db
+      = new (ggc_alloc <string_concat_db> ()) string_concat_db ();
+
+  static struct cpp_reader* parse_in;
+  if (!parse_in)
+    {
+      /* Create and initialize a preprocessing reader.  */
+      parse_in = cpp_create_reader (CLK_GNUC99, ident_hash, line_table);
+      cpp_init_iconv (parse_in);
+    }
+
+  return get_source_location_for_substring (parse_in, g_string_concat_db,
+					    m_fmt_string_loc, CPP_STRING,
+					    m_caret_idx, m_start_idx, m_end_idx,
+					    out_loc);
+}
+
+static ATTRIBUTE_GCC_DIAG (5,0) bool
+format_warning_va (const substring_loc &fmt_loc,
+		   const source_range *param_range,
+		   const char *corrected_substring,
+		   int opt, const char *gmsgid, va_list *ap)
+{
+  bool substring_within_range = false;
+  location_t primary_loc;
+  location_t fmt_substring_loc = UNKNOWN_LOCATION;
+  source_range fmt_loc_range
+    = get_range_from_loc (line_table, fmt_loc.get_fmt_string_loc ());
+  const char *err = fmt_loc.get_location (&fmt_substring_loc);
+  source_range fmt_substring_range
+    = get_range_from_loc (line_table, fmt_substring_loc);
+  if (err)
+    /* Case 3: unable to get substring location.  */
+    primary_loc = fmt_loc.get_fmt_string_loc ();
+  else
+    {
+      if (fmt_substring_range.m_start >= fmt_loc_range.m_start
+	  && fmt_substring_range.m_finish <= fmt_loc_range.m_finish)
+	/* Case 1.  */
+	{
+	  substring_within_range = true;
+	  primary_loc = fmt_substring_loc;
+	}
+      else
+	/* Case 2.  */
+	{
+	  substring_within_range = false;
+	  primary_loc = fmt_loc.get_fmt_string_loc ();
+	}
+    }
+
+  rich_location richloc (line_table, primary_loc);
+
+  if (param_range)
+    {
+      location_t param_loc = make_location (param_range->m_start,
+					    param_range->m_start,
+					    param_range->m_finish);
+      richloc.add_range (param_loc, false);
+    }
+
+  if (!err && corrected_substring && substring_within_range)
+    richloc.add_fixit_replace (fmt_substring_range, corrected_substring);
+
+  diagnostic_info diagnostic;
+  diagnostic_set_info (&diagnostic, gmsgid, ap, &richloc, DK_WARNING);
+  diagnostic.option_index = opt;
+  bool warned = report_diagnostic (&diagnostic);
+
+  if (!err && fmt_substring_loc && !substring_within_range)
+    /* Case 2.  */
+    if (warned)
+      {
+	rich_location substring_richloc (line_table, fmt_substring_loc);
+	if (corrected_substring)
+	  substring_richloc.add_fixit_replace (fmt_substring_range,
+					       corrected_substring);
+	inform_at_rich_loc (&substring_richloc,
+			    "format string is defined here");
+      }
+
+  return warned;
+}
+
+static ATTRIBUTE_GCC_DIAG (5,0) bool
+fmtwarn (const substring_loc &fmt_loc,
+	 const source_range *param_range,
+	 const char *corrected_substring,
+	 int opt, const char *gmsgid, ...)
+{
+  va_list ap;
+  va_start (ap, gmsgid);
+  bool warned = format_warning_va (fmt_loc, param_range, corrected_substring,
+				   opt, gmsgid, &ap);
+  va_end (ap);
+
+  return warned;
+}
+
+/* Format length modifiers.  */
+
+enum format_lengths
+{
+  FMT_LEN_none,
+  FMT_LEN_hh,    // char argument
+  FMT_LEN_h,     // short
+  FMT_LEN_l,     // long
+  FMT_LEN_ll,    // long long
+  FMT_LEN_L,     // long double (and GNU long long)
+  FMT_LEN_z,     // size_t
+  FMT_LEN_t,     // ptrdiff_t
+  FMT_LEN_j      // intmax_t
+};
+
+
+/* A minimum and maximum number of bytes.  */
+
+struct result_range
+{
+  unsigned HOST_WIDE_INT min, max;
+};
+
+/* Description of the result of conversion either of a single directive
+   or the whole format string.  */
+
+struct fmtresult
+{
+  /* The range a directive's argument is in.  */
+  tree argmin, argmax;
+
+  /* The minimum and maximum number of bytes that a directive
+     results in on output for an argument in the range above.  */
+  result_range range;
+
+  /* True when the range is the result of an argument determined
+     to be bounded to a subrange of its type or value (such as by
+     value range propagation or the width of the formt directive),
+     false otherwise.  */
+  bool bounded;
+  /* True when the output of a directive is constant.  This is rare
+     and typically requires that the argument(s) of the directive
+     are also constant (such as determined by constant propagation,
+     though not value range propagation).  */
+  bool constant;
+};
+
+/* Description of a conversion specification.  */
+
+struct conversion_spec
+{
+  /* A bitmap of flags, one for each character.  */
+  unsigned flags[256 / sizeof (int)];
+  /* Numeric width as in "%8x".  */
+  int width;
+  /* Numeric precision as in "%.32s".  */
+  int precision;
+
+  /* Width specified via the '*' character.  */
+  tree star_width;
+  /* Precision specified via the asterisk.  */
+  tree star_precision;
+
+  /* Length modifier.  */
+  format_lengths modifier;
+
+  /* Format specifier character.  */
+  char specifier;
+
+  /* Numeric width was given.  */
+  unsigned have_width: 1;
+  /* Numeric precision was given.  */
+  unsigned have_precision: 1;
+  /* Non-zero when certain flags should be interpreted even for a directive
+     that normally doesn't accept them (used when "%p" with flags such as
+     space or plus is interepreted as a "%x".  */
+  unsigned force_flags: 1;
+
+  /* Format conversion function that given a conversion specification
+     and an argument returns the formatting result.  */
+  fmtresult  (*fmtfunc) (const conversion_spec &, tree);
+
+  /* Return True when a the format flag CHR has been used.  */
+  bool get_flag (char chr) const
+  {
+    unsigned char c = chr & 0xff;
+    return (flags[c / (CHAR_BIT * sizeof *flags)]
+	    & (1U << (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)]
+      |= (1U << (c % (CHAR_BIT * sizeof *flags)));
+  }
+
+  /* Reset the format flag CHR.  */
+  void clear_flag (char chr)
+  {
+    unsigned char c = chr & 0xff;
+    flags[c / (CHAR_BIT * sizeof *flags)]
+      &= ~(1U << (c % (CHAR_BIT * sizeof *flags)));
+  }
+};
+
+/* Return the logarithm of X in BASE.  */
+
+static int
+ilog (unsigned HOST_WIDE_INT x, int base)
+{
+  int res = 0;
+  do {
+    ++res;
+    x /= base;
+  } while (x);
+  return res;
+}
+
+/* Return the number of bytes resulting from converting into a string
+   the INTEGER_CST tree node X in BASE.  PLUS indicates whether 1 for
+   a plus sign should be added for positive numbers, and PREFIX whether
+   the length of an octal ('O') or hexadecimal ('0x') prefix should be
+   added for nonzero numbers.  Return -1 if X cannot be represented.  */
+
+static int
+tree_digits (tree x, int base, bool plus, bool prefix)
+{
+  unsigned HOST_WIDE_INT absval;
+
+  int res;
+
+  if (TYPE_UNSIGNED (TREE_TYPE (x)))
+    {
+      if (tree_fits_uhwi_p (x))
+	{
+	  absval = tree_to_uhwi (x);
+	  res = plus;
+	}
+      else
+	return -1;
+    }
+  else
+    {
+      if (tree_fits_shwi_p (x))
+	{
+	  HOST_WIDE_INT i = tree_to_shwi (x);
+	  if (i < 0)
+	    {
+	      absval = -i;
+	      res = 1;
+	    }
+	  else
+	    {
+	      absval = i;
+	      res = plus;
+	    }
+	}
+      else
+	return -1;
+    }
+
+  res += ilog (absval, base);
+
+  if (prefix && absval)
+    {
+      if (base == 8)
+	res += 1;
+      else if (base == 16)
+	res += 2;
+    }
+
+  return res;
+}
+
+/* Given the formatting result described by RES and NAVAIL, the number
+   of available in the destination, return the number of bytes remaining
+   in the destination.  */
+
+static inline result_range
+bytes_remaining (unsigned HOST_WIDE_INT navail, const format_result &res)
+{
+  result_range range;
+
+  if (HOST_WIDE_INT_MAX <= navail)
+    {
+      range.min = navail;
+      range.min = navail;
+      return range;
+    }
+
+  if (res.number_chars < navail)
+    {
+      range.min = navail - res.number_chars;
+      range.max = navail - res.number_chars;
+    }
+  else if (res.number_chars_min < navail)
+    {
+      range.max = navail - res.number_chars_min;
+    }
+  else
+    range.max = 0;
+
+  if (res.number_chars_max < navail)
+    range.min = navail - res.number_chars_max;
+  else
+    range.min = 0;
+
+  return range;
+}
+
+/* Given the formatting result described by RES and NAVAIL, the number
+   of available in the destination, return the minimum number of bytes
+   remaining in the destination.  */
+
+static inline unsigned HOST_WIDE_INT
+min_bytes_remaining (unsigned HOST_WIDE_INT navail, const format_result &res)
+{
+  if (HOST_WIDE_INT_MAX <= navail)
+    return navail;
+
+  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 (res.number_chars_max < HOST_WIDE_INT_MAX)
+	navail -= res.number_chars_max;
+      else if (res.number_chars < HOST_WIDE_INT_MAX)
+	navail -= res.number_chars;
+      else if (res.number_chars_min < HOST_WIDE_INT_MAX)
+	navail -= res.number_chars_min;
+    }
+  else
+    {
+      /* At level 1 use the smaller of the byte counters to compute
+	 the result.  */
+      if (res.number_chars < HOST_WIDE_INT_MAX)
+	navail -= res.number_chars;
+      else if (res.number_chars_min < HOST_WIDE_INT_MAX)
+	navail -= res.number_chars_min;
+      else if (res.number_chars_max < HOST_WIDE_INT_MAX)
+	navail -= res.number_chars_max;
+    }
+
+  if (navail > HOST_WIDE_INT_MAX)
+    navail = 0;
+
+  return navail;
+}
+
+/* Description of a call to a formatting function.  */
+
+struct pass_sprintf_length::call_info
+{
+  /* Function call statement.  */
+  gimple *callstmt;
+
+  /* Function called.  */
+  tree func;
+
+  /* Called built-in function code.  */
+  built_in_function fncode;
+
+  /* Format argument and format string extracted from it.  */
+  tree format;
+  const char *fmtstr;
+
+  /* The location of the format argument.  */
+  location_t fmtloc;
+
+  /* The destination object size for __builtin___xxx_chk functions
+     typically determined by __builtin_object_size, or -1 if unknown.  */
+  unsigned HOST_WIDE_INT objsize;
+
+  /* Number of the first variable argument.  */
+  unsigned HOST_WIDE_INT argidx;
+
+  /* True for functions like snprintf that specify the size of
+     the destination, false for others like sprintf that don't.  */
+  bool bounded;
+};
+
+/* Return the result of formatting the '%%' directive.  */
+
+static fmtresult
+format_percent (const conversion_spec &, tree)
+{
+  fmtresult res;
+  res.argmin = res.argmax = NULL_TREE;
+  res.range.min = res.range.max = 1;
+  res.bounded = res.constant = true;
+  return res;
+}
+
+
+/* Ugh.  Compute intmax_type_node and uintmax_type_node the same way
+   lto/lto-lang.c does it.  This should be available in tree.h.  */
+
+static void
+build_intmax_type_nodes (tree *pintmax, tree *puintmax)
+{
+  if (strcmp (SIZE_TYPE, "unsigned int") == 0)
+    {
+      *pintmax = integer_type_node;
+      *puintmax = unsigned_type_node;
+    }
+  else if (strcmp (SIZE_TYPE, "long unsigned int") == 0)
+    {
+      *pintmax = long_integer_type_node;
+      *puintmax = long_unsigned_type_node;
+    }
+  else if (strcmp (SIZE_TYPE, "long long unsigned int") == 0)
+    {
+      *pintmax = long_long_integer_type_node;
+      *puintmax = long_long_unsigned_type_node;
+    }
+  else
+    {
+      for (int i = 0; i < NUM_INT_N_ENTS; i++)
+        if (int_n_enabled_p[i])
+          {
+            char name[50];
+            sprintf (name, "__int%d unsigned", int_n_data[i].bitsize);
+
+            if (strcmp (name, SIZE_TYPE) == 0)
+              {
+                *pintmax = int_n_trees[i].signed_type;
+                *puintmax = int_n_trees[i].unsigned_type;
+              }
+          }
+    }
+}
+
+static fmtresult
+format_integer (const conversion_spec &, tree);
+
+/* Return a range representing the minimum and maximum number of bytes
+   that the conversion specification SPEC will write on output for the
+   pointer argument ARG when non-null.  ARG may be null (for vararg
+   functions).  */
+
+static fmtresult
+format_pointer (const conversion_spec &spec, tree arg)
+{
+  fmtresult res = fmtresult ();
+
+  /* Determine the target's integer format corresponding to "%p".  */
+  const char *flags;
+  const char *pfmt = TARGET_LIBC_PRINTF_POINTER_FORMAT (arg, &flags);
+  if (!pfmt)
+    {
+      /* The format couldn't be determined.  */
+      res.range.min = res.range.max = -1;
+      return res;
+    }
+
+  if (pfmt [0] == '%')
+    {
+      /* Format the pointer using the integer format string.  */
+      conversion_spec pspec = spec;
+
+      /* Clear flags that are not listed as recognized.  */
+      for (const char *pf = "+ #0"; *pf; ++pf)
+	{
+	  if (!strchr (flags, *pf))
+	    pspec.clear_flag (*pf);
+	}
+
+      /* Set flags that are specified in the format string.  */
+      bool flag_p = true;
+      do {
+	switch (*++pfmt)
+	  {
+	  case '+': case ' ': case '#': case '0':
+	    pspec.set_flag (*pfmt);
+	    break;
+	  default:
+	    flag_p = false;
+	  }
+      }
+      while (flag_p);
+
+      /* Set the appropriate length modifier taking care to clear
+       the one that may be set (Glibc's %p accepts but ignores all
+       the integer length modifiers).  */
+      switch (*pfmt)
+	{
+	case 'l': pspec.modifier = FMT_LEN_l; ++pfmt; break;
+	case 't': pspec.modifier = FMT_LEN_t; ++pfmt; break;
+	case 'z': pspec.modifier = FMT_LEN_z; ++pfmt; break;
+	default: pspec.modifier = FMT_LEN_none;
+	}
+
+      pspec.force_flags = 1;
+      pspec.specifier = *pfmt++;
+      gcc_assert (*pfmt == '\0');
+      return format_integer (pspec, arg);
+    }
+
+  /* The format is a plain string (such as Glibc's "(nil)".  */
+  res.range.min = res.range.max = strlen (pfmt);
+  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 when non-null.  ARG may be null (for vararg
+   functions).  */
+
+static fmtresult
+format_integer (const conversion_spec &spec, tree arg)
+{
+  /* These are available as macros in the C and C++ front ends but,
+     sadly, not here.  */
+  static tree intmax_type_node;
+  static tree uintmax_type_node;
+
+  /* Initialize the intmax nodes above the first time through here.  */
+  if (!intmax_type_node)
+    build_intmax_type_nodes (&intmax_type_node, &uintmax_type_node);
+
+  /* Set WIDTH and PRECISION to either the values in the format
+     specification or to zero.  */
+  int width = spec.have_width ? spec.width : 0;
+  int prec = spec.have_precision ? spec.precision : 0;
+
+  if (spec.star_width)
+    width = (TREE_CODE (spec.star_width) == INTEGER_CST
+	     ? tree_to_shwi (spec.star_width) : 0);
+
+  if (spec.star_precision)
+    prec = (TREE_CODE (spec.star_precision) == INTEGER_CST
+	    ? tree_to_shwi (spec.star_precision) : 0);
+
+  bool sign = spec.specifier == 'd' || spec.specifier == 'i';
+
+  /* The type of the "formal" argument expected by the directive.  */
+  tree dirtype = NULL_TREE;
+
+  /* Determine the expected type of the argument from the length
+     modifier.  */
+  switch (spec.modifier)
+    {
+    case FMT_LEN_none:
+      if (spec.specifier == 'p')
+	dirtype = ptr_type_node;
+      else
+	dirtype = sign ? integer_type_node : unsigned_type_node;
+      break;
+
+    case FMT_LEN_h:
+      dirtype = sign ? short_integer_type_node : short_unsigned_type_node;
+      break;
+
+    case FMT_LEN_hh:
+      dirtype = sign ? signed_char_type_node : unsigned_char_type_node;
+      break;
+
+    case FMT_LEN_l:
+      dirtype = sign ? long_integer_type_node : long_unsigned_type_node;
+      break;
+
+    case FMT_LEN_L:
+    case FMT_LEN_ll:
+      dirtype = sign ? long_integer_type_node : long_unsigned_type_node;
+      break;
+
+    case FMT_LEN_z:
+      dirtype = sign ? ptrdiff_type_node : size_type_node;
+      break;
+
+    case FMT_LEN_t:
+      dirtype = sign ? ptrdiff_type_node : size_type_node;
+      break;
+
+    case FMT_LEN_j:
+      dirtype = sign ? intmax_type_node : uintmax_type_node;
+      break;
+
+    default:
+      gcc_unreachable ();
+    }
+
+  /* The type of the argument to the directive, either deduced from
+     the actual non-constant argument if one is known, or from
+     the directive itself when none has been provided because it's
+     a va_list.  */
+  tree argtype = NULL_TREE;
+
+  if (!arg)
+    {
+      /* When the argument has not been provided, use the type of
+	 the directive's argument as an approximation.  This will
+	 result in false positives for directives like %i with
+	 arguments with smaller precision (such as short or char).  */
+      argtype = dirtype;
+    }
+  else if (TREE_CODE (arg) == INTEGER_CST)
+    {
+      /* The minimum and maximum number of bytes produced by
+	 the directive.  */
+      fmtresult res = fmtresult ();
+
+      /* When a constant argument has been provided use its value
+	 rather than type to determine the length of the output.  */
+      res.bounded = true;
+      res.constant = true;
+
+      /* Base to format the number in.  */
+      int base;
+
+      /* True when a signed conversion is preceded by a sign or space.  */
+      bool maybesign;
+
+      switch (spec.specifier)
+	{
+	case 'd':
+	case 'i':
+	  /* Space is only effective for signed conversions.  */
+	  maybesign = spec.get_flag (' ');
+	  base = 10;
+	  break;
+	case 'u':
+	  maybesign = spec.force_flags ? spec.get_flag (' ') : false;
+	  base = 10;
+	  break;
+	case 'o':
+	  maybesign = spec.force_flags ? spec.get_flag (' ') : false;
+	  base = 8;
+	  break;
+	case 'X':
+	case 'x':
+	  maybesign = spec.force_flags ? spec.get_flag (' ') : false;
+	  base = 16;
+	  break;
+	default:
+	  gcc_unreachable ();
+	}
+
+      /* Convert the argument to the type of the directive.  */
+      arg = fold_convert (dirtype, arg);
+
+      maybesign |= spec.get_flag ('+');
+
+      /* True when a conversion is preceded by a prefix indicating the base
+	 of the argument (octal or hexadecimal).  */
+      bool maybebase = spec.get_flag ('#');
+      int len = tree_digits (arg, base, maybesign, maybebase);
+
+      if (len < prec)
+	len = prec;
+
+      if (len < width)
+	len = width;
+
+      res.range.max = len;
+      res.range.min = res.range.max;
+      res.bounded = true;
+
+      return res;
+    }
+  else if (TREE_CODE (TREE_TYPE (arg)) == INTEGER_TYPE
+	   || TREE_CODE (TREE_TYPE (arg)) == POINTER_TYPE)
+    {
+      /* Determine the type of the provided non-constant argument.  */
+      if (TREE_CODE (arg) == NOP_EXPR)
+	arg = TREE_OPERAND (arg, 0);
+      else if (TREE_CODE (arg) == CONVERT_EXPR)
+	arg = TREE_OPERAND (arg, 0);
+      if (TREE_CODE (arg) == COMPONENT_REF)
+	arg = TREE_OPERAND (arg, 1);
+
+      argtype = TREE_TYPE (arg);
+    }
+  else
+    {
+      /* Don't bother with invalid arguments since they likely would
+	 have already been diagnosed, and disable any further checking
+	 of the format string by returning [-1, -1].  */
+      fmtresult res = fmtresult ();
+      res.range.min = res.range.max = -1;
+      return res;
+    }
+
+  fmtresult res = fmtresult ();
+
+  /* Using either the range the non-constant argument is in, or its
+     type (either "formal" or actual), create a range of values that
+     constrain the length of output given the warning level.  */
+  tree argmin = NULL_TREE;
+  tree argmax = NULL_TREE;
+
+  if (arg && TREE_CODE (arg) == SSA_NAME
+      && TREE_CODE (argtype) == INTEGER_TYPE)
+    {
+      /* Try to determine the range of values of the integer argument
+	 (range information is not available for pointers).  */
+      wide_int min, max;
+      enum value_range_type range_type = get_range_info (arg, &min, &max);
+      if (range_type == VR_RANGE)
+	{
+	  res.argmin = build_int_cst (argtype, wi::fits_uhwi_p (min)
+				      ? min.to_uhwi () : min.to_shwi ());
+	  res.argmax = build_int_cst (argtype, wi::fits_uhwi_p (max)
+				      ? max.to_uhwi () : max.to_shwi ());
+
+	  /* For a range with a negative lower bound and a non-negative
+	     upper bound, use one to determine the minimum number of bytes
+	     on output and whichever of the two bounds that results in
+	     the larger number of bytes on output for the upper bound.
+	     For example, for arg in the range of [-3, 123], use 123 as
+	     the upper bound for %i but -3 for %u.  */
+	  if (wi::neg_p (min) && !wi::neg_p (max))
+	    {
+	      argmin = build_int_cst (argtype, wi::fits_uhwi_p (min)
+				      ? min.to_uhwi () : min.to_shwi ());
+
+	      argmax = build_int_cst (argtype, wi::fits_uhwi_p (max)
+				      ? max.to_uhwi () : max.to_shwi ());
+
+	      int minbytes = format_integer (spec, res.argmin).range.min;
+	      int maxbytes = format_integer (spec, res.argmax).range.max;
+	      if (maxbytes < minbytes)
+		argmax = res.argmin;
+
+	      argmin = integer_zero_node;
+	    }
+	  else
+	    {
+	      argmin = res.argmin;
+	      argmax = res.argmax;
+	    }
+
+	  /* The argument is bounded by the range of values determined
+	     by the Value Range Propagation.  */
+	  res.bounded = true;
+	}
+      else if (range_type == VR_ANTI_RANGE)
+	{
+	  /* Handle anti-ranges if/when bug 71690 is ever resolved.  */
+	}
+      else if (range_type == VR_VARYING)
+	{
+	  /* The argument here may be the result of promoting
+	     the actual argument to int.  Try to determine the
+	     type of the actual argument before promotion and
+	     narrow down its range that way.  */
+	  gimple *def = SSA_NAME_DEF_STMT (arg);
+	  if (gimple_code (def) == GIMPLE_ASSIGN)
+	    {
+	      tree_code code = gimple_assign_rhs_code (def);
+	      if (code == NOP_EXPR)
+		argtype = TREE_TYPE (gimple_assign_rhs1 (def));
+	    }
+	}
+    }
+
+  if (!argmin)
+    {
+      /* For an unknown argument (e.g., one passed to a vararg
+	 function) or one whose value range cannot be determined,
+	 create a T_MIN constant if the argument's type is signed
+	 and T_MAX otherwise, and use those to compute the range
+	 of bytes that the directive can output.  */
+      argmin = build_int_cst (argtype, 1);
+
+      int typeprec = TYPE_PRECISION (dirtype);
+      int argprec = TYPE_PRECISION (argtype);
+
+      if (argprec < typeprec || POINTER_TYPE_P (argtype))
+	{
+	  if (TYPE_UNSIGNED (argtype))
+	    argmax = build_all_ones_cst (argtype);
+	  else
+	    argmax = fold_build2 (LSHIFT_EXPR, argtype, integer_one_node,
+				  build_int_cst (integer_type_node,
+						 argprec - 1));
+	}
+      else
+	{
+	  argmax = fold_build2 (LSHIFT_EXPR, dirtype, integer_one_node,
+				build_int_cst (integer_type_node,
+					       typeprec - 1));
+	}
+      res.argmin = argmin;
+      res.argmax = argmax;
+    }
+
+  /* Recursively compute the minimum and maximum from the known range,
+     taking care to swap them if the lower bound results in longer
+     output than the upper bound (e.g., in the range [-1, 0].  */
+  res.range.min = format_integer (spec, argmin).range.min;
+  res.range.max = format_integer (spec, argmax).range.max;
+
+  /* The result is bounded either when the argument determined to be
+     (e.g., when it's within some range) or when the minimum and maximum
+     are the same.  That can happen here for example when the specified
+     width is as wide as the greater of MIN and MAX, as would be the case
+     with sprintf (d, "%08x", x) with a 32-bit integer x.  */
+  res.bounded |= res.range.min == res.range.max;
+
+  if (res.range.max < res.range.min)
+    {
+      unsigned HOST_WIDE_INT tmp = res.range.max;
+      res.range.max = res.range.min;
+      res.range.min = tmp;
+    }
+
+  return res;
+}
+
+/* Return the number of bytes to format using the format specifier
+   SPEC the largest value in the real floating TYPE.  */
+
+static int
+format_floating_max (tree type, char spec)
+{
+  machine_mode mode = TYPE_MODE (type);
+
+  /* IBM Extended mode.  */
+  if (MODE_COMPOSITE_P (mode))
+    mode = DFmode;
+
+  /* Get the real type format desription for the target.  */
+  const real_format *rfmt = REAL_MODE_FORMAT (mode);
+  REAL_VALUE_TYPE rv;
+
+  {
+    char buf [256];
+    get_max_float (rfmt, buf, sizeof buf);
+    real_from_string (&rv, buf);
+  }
+
+  /* Convert the GCC real value representation with the precision
+     of the real type to the mpfr_t format with the GCC default
+     round-to-nearest mode.  */
+  mpfr_t x;
+  mpfr_init2 (x, rfmt->p);
+  mpfr_from_real (x, &rv, MPFR_RNDN);
+
+  const char fmt [] = { '%', 'R', spec, '\0' };
+  int n = mpfr_snprintf (NULL, 0, fmt, x);
+  return n;
+}
+
+/* Return a range representing the minimum and maximum number of bytes
+   that the conversion specification SPEC will output for any argument
+   given the WIDTH and PRECISION (extracted from SPEC).  This function
+   is used when the directive argument or its value isn't known.  */
+
+static fmtresult
+format_floating (const conversion_spec &spec, int width, int prec)
+{
+  tree type;
+  bool ldbl = false;
+
+  switch (spec.modifier)
+    {
+    case FMT_LEN_none:
+      type = double_type_node;
+      break;
+
+    case FMT_LEN_L:
+      type = long_double_type_node;
+      ldbl = true;
+      break;
+
+    case FMT_LEN_ll:
+      type = long_double_type_node;
+      ldbl = true;
+      break;
+
+    default:
+      gcc_unreachable ();
+    }
+
+  /* The minimum and maximum number of bytes produced by the directive.  */
+  fmtresult res = fmtresult ();
+  res.constant = false;
+
+  /* Log10 of of the maximum number of exponent digits for the type.  */
+  int logexpdigs = 2;
+
+  if (REAL_MODE_FORMAT (TYPE_MODE (type))->b == 2)
+    {
+      /* The base in which the exponent is represented should always
+	 be 2 in GCC.  */
+
+      const double log10_2 = .30102999566398119521;
+
+      /* Compute T_MAX_EXP for base 2.  */
+      int expdigs = REAL_MODE_FORMAT (TYPE_MODE (type))->emax * log10_2;
+      logexpdigs = ilog (expdigs, 10);
+    }
+
+  switch (spec.specifier)
+    {
+    case 'A':
+    case 'a':
+      {
+	/* The minimum output is "0x.p+0".  */
+	res.range.min = 6 + (0 < prec ? prec : 0);
+
+	/* Compute the maximum just once.  */
+	static const int a_max[] = {
+	  format_floating_max (double_type_node, 'a'),
+	  format_floating_max (long_double_type_node, 'a')
+	};
+	res.range.max = a_max [ldbl];
+	break;
+      }
+
+    case 'E':
+    case 'e':
+      {
+	bool sign = spec.get_flag ('+') || spec.get_flag (' ');
+	/* The minimum output is "[-+]1.234567e+00" regardless
+	   of the value of the actual argument. */
+	res.range.min = (sign
+			 + 1 /* unit */ + (prec < 0 ? 7 : prec ? prec + 1 : 0)
+			 + 2 /* e+ */ + 2);
+	/* The maximum output is the minimum plus sign (unless already
+	   included), plus the difference between the minimum exponent
+	   of 2 and the maximum exponent for the type.  */
+	res.range.max = res.range.min + !sign + logexpdigs - 2;
+	break;
+      }
+
+    case 'F':
+    case 'f':
+      {
+	/* The minimum output is "1.234567" regardless of the value
+	   of the actual argument. */
+	res.range.min = 2 + (prec < 0 ? 6 : prec);
+
+	/* Compute the maximum just once.  */
+	static const int f_max[] = {
+	  format_floating_max (double_type_node, 'f'),
+	  format_floating_max (long_double_type_node, 'f')
+	};
+	res.range.max = f_max [ldbl];
+	break;
+      }
+    case 'G':
+    case 'g':
+      {
+	/* The minimum is the same as for '%F'.  */
+	res.range.min = 2 + (prec < 0 ? 6 : prec);
+
+	/* Compute the maximum just once.  */
+	static const int g_max[] = {
+	  format_floating_max (double_type_node, 'g'),
+	  format_floating_max (long_double_type_node, 'g')
+	};
+	res.range.max = g_max [ldbl];
+	break;
+      }
+
+    default:
+      gcc_unreachable ();
+    }
+
+  if (0 < width)
+    {
+      if (res.range.min < (unsigned)width)
+	res.range.min = width;
+      if (res.range.max < (unsigned)width)
+	res.range.max = width;
+    }
+
+  /* The argument is only considered bounded when the range of output
+     bytes is exact.  */
+  res.bounded = res.range.min == res.range.max;
+  return res;
+}
+
+/* Return a range representing the minimum and maximum number of bytes
+   that the conversion specification SPEC will write on output for the
+   floating argument ARG.  */
+
+static fmtresult
+format_floating (const conversion_spec &spec, tree arg)
+{
+  int width = -1;
+  int prec = -1;
+
+  /* The minimum and maximum number of bytes produced by the directive.  */
+  fmtresult res = fmtresult ();
+  res.constant = arg && TREE_CODE (arg) == REAL_CST;
+
+  if (spec.have_width)
+    width = spec.width;
+  else if (spec.star_width)
+    {
+      if (TREE_CODE (spec.star_width) == INTEGER_CST)
+	width = tree_to_shwi (spec.star_width);
+      else
+	{
+	  res.range.min = res.range.max = -1;
+	  return res;
+	}
+    }
+
+  if (spec.have_precision)
+    prec = spec.precision;
+  else if (spec.star_precision)
+    {
+      if (TREE_CODE (spec.star_precision) == INTEGER_CST)
+	prec = tree_to_shwi (spec.star_precision);
+      else
+	{
+	  res.range.min = res.range.max = -1;
+	  return res;
+	}
+    }
+  else if (res.constant && TOUPPER (spec.specifier) != 'A')
+    {
+      /* Specify the precision explicitly since mpfr_sprintf defaults
+	 to zero.  */
+      prec = 6;
+    }
+
+  if (res.constant)
+    {
+      /* Set up an array to easily iterate over.  */
+      unsigned HOST_WIDE_INT* const minmax[] = {
+	&res.range.min, &res.range.max
+      };
+
+      /* Get the real type format desription for the target.  */
+      const REAL_VALUE_TYPE *rvp = TREE_REAL_CST_PTR (arg);
+      const real_format *rfmt = REAL_MODE_FORMAT (TYPE_MODE (TREE_TYPE (arg)));
+
+      /* Convert the GCC real value representation with the precision
+	 of the real type to the mpfr_t format with the GCC default
+	 round-to-nearest mode.  */
+      mpfr_t mpfrval;
+      mpfr_init2 (mpfrval, rfmt->p);
+      mpfr_from_real (mpfrval, rvp, MPFR_RNDN);
+
+      char fmtstr [40];
+      char *pfmt = fmtstr;
+      *pfmt++ = '%';
+
+      /* Append flags.  */
+      for (const char *pf = "-+ #0"; *pf; ++pf)
+	if (spec.get_flag (*pf))
+	  *pfmt++ = *pf;
+
+      /* Append width when specified and precision.  */
+      if (width != -1)
+	pfmt += sprintf (pfmt, "%i", width);
+      if (prec != -1)
+	pfmt += sprintf (pfmt, ".%i", prec);
+
+      /* Append the MPFR 'R' floating type specifier (no length modifier
+	 is necessary or allowed by MPFR for mpfr_t values).  */
+      *pfmt++ = 'R';
+
+      /* Save the position of the MPFR rounding specifier and skip over
+	 it.  It will be set in each iteration in the loop below.  */
+      char* const rndspec = pfmt++;
+
+      /* Append the C type specifier and nul-terminate.  */
+      *pfmt++ = spec.specifier;
+      *pfmt = '\0';
+
+      for (int i = 0; i != sizeof minmax / sizeof *minmax; ++i)
+	{
+	  /* Use the MPFR rounding specifier to round down in the first
+	     iteration and then up.  In most but not all cases this will
+	     result in the same number of bytes.  */
+	  *rndspec = "DU"[i];
+
+	  /* Format it and store the result in the corresponding
+	     member of the result struct.  */
+	  *minmax[i] = mpfr_snprintf (NULL, 0, fmtstr, mpfrval);
+	}
+
+      res.bounded = res.range.min < HOST_WIDE_INT_MAX;
+      return res;
+    }
+
+  return format_floating (spec, width, prec);
+}
+
+/* Return a FMTRESULT struct set to the lengths of the shortest and longest
+   strings referenced by the expression STR, or (-1, -1) when not known.
+   Used by the format_string function below.  */
+
+static fmtresult
+get_string_length (tree str)
+{
+  if (!str)
+    {
+      fmtresult res;
+      res.range.min = HOST_WIDE_INT_MAX;
+      res.range.max = HOST_WIDE_INT_MAX;
+      res.bounded = false;
+      res.constant = false;
+      return res;
+    }
+
+  if (tree slen = c_strlen (str, 1))
+    {
+      /* Simply return the length of the string.  */
+      fmtresult res;
+      res.range.min = res.range.max = tree_to_shwi (slen);
+      res.bounded = true;
+      res.constant = true;
+      return res;
+    }
+
+  tree lenrange[2];
+  get_range_strlen (str, lenrange);
+
+  if (lenrange [0] || lenrange [1])
+    {
+      fmtresult res = fmtresult ();
+
+      res.range.min = (tree_fits_uhwi_p (lenrange[0])
+		       ? tree_to_uhwi (lenrange[0]) : 1 < warn_format_length);
+      res.range.max = (tree_fits_uhwi_p (lenrange[1])
+		       ? tree_to_uhwi (lenrange[1]) : HOST_WIDE_INT_MAX);
+
+      res.bounded = res.range.max != HOST_WIDE_INT_MAX;
+      res.constant = false;
+      return res;
+    }
+
+  return get_string_length (NULL_TREE);
+}
+
+/* Return the minimum and maximum number of characters formatted
+   by the '%c' and '%s' format directives and ther wide character
+   forms for the argument ARG.  ARG can be null (for functions
+   such as vsprinf).  */
+
+static fmtresult
+format_string (const conversion_spec &spec, tree arg)
+{
+  unsigned width = spec.have_width && 0 < spec.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 (width > INT_MAX)
+	width = 0;
+    }
+
+  if (spec.star_precision)
+    prec = (TREE_CODE (spec.star_precision) == INTEGER_CST
+	    ? tree_to_shwi (spec.star_precision) : -1);
+
+  fmtresult res = fmtresult ();
+
+  /* The maximum number of bytes for an unknown wide character argument
+     to a "%lc" directive adjusted for precision but not field width.  */
+  const unsigned HOST_WIDE_INT max_bytes_for_unknown_wc
+    = (1 == warn_format_length ? 0 <= prec ? prec : 0
+       : 2 == warn_format_length ? 0 <= prec ? prec : 1
+       : 0 <= prec ? prec : 6 /* Longest UTF-8 sequence. */);
+
+  /* The maximum number of bytes for an unknown string argument to either
+     a "%s" or "%ls" directive adjusted for precision but not field width.  */
+  const unsigned HOST_WIDE_INT max_bytes_for_unknown_str
+    = (1 == warn_format_length ? 0 <= prec ? prec : 0
+       : 2 == warn_format_length ? 0 <= prec ? prec : 1
+       : HOST_WIDE_INT_MAX);
+
+  if (spec.specifier == 'c')
+    {
+      if (spec.modifier == FMT_LEN_l)
+	{
+	  /* Positive if the argument is a wide NUL character?  */
+	  int nul = (arg && TREE_CODE (arg) == INTEGER_CST
+		     ? integer_zerop (arg) : -1);
+
+	  /* A '%lc' directive is the same as '%ls' for a two element
+	     wide string character with the second element of NUL, so
+	     when the character is unknown the minimum number of bytes
+	     is the smaller of either 0 (at level 1) or 1 (at level 2)
+	     and WIDTH, and the maximum is MB_CUR_MAX in the selected
+	     locale, which is unfortunately, unknown.  */
+	  res.range.min = 1 == warn_format_length ? !nul : nul < 1;
+	  res.range.max = max_bytes_for_unknown_wc;
+	  res.bounded = true;
+	}
+      else
+	{
+	  /* A plain '%c' directive.  */
+	  res.range.min = res.range.max = 1;
+	  res.bounded = true;
+	  res.constant = arg && TREE_CODE (arg) == INTEGER_CST;
+	}
+    }
+  else   /* spec.specifier == 's' */
+    {
+      /* Compute the range the argument's length can be in.  */
+      fmtresult slen = get_string_length (arg);
+      if (slen.constant)
+	{
+	  gcc_checking_assert (slen.range.min == slen.range.max);
+
+	  res.bounded = true;
+
+	  /* A '%s' directive with a string argument with constant length.  */
+	  res.range = slen.range;
+
+	  if (spec.modifier == FMT_LEN_l)
+	    {
+	      if (warn_format_length > 2)
+		{
+		  res.range.min *= 6;
+
+		  /* It's possible to be smarter about computing the maximum
+		     by scanning the wide string for any 8-bit characters and
+		     if it contains none, using its length for the maximum.
+		     Even though this would be simple to do it's unlikely to
+		     be worth it when dealing with wide characters.  */
+		  res.range.max *= 6;
+		}
+	      /* For a wide character string, use precision as the maximum
+		 even if precision is greater than the string length since
+		 the number of bytes the string converts to may be greater
+		 (due to MB_CUR_MAX).  */
+	      if (0 <= prec)
+		res.range.max = prec;
+	    }
+	  else
+	    res.constant = true;
+
+	  if (0 <= prec && (unsigned)prec < res.range.min)
+	    {
+	      res.range.min = prec;
+	      res.range.max = prec;
+	    }
+	}
+      else
+	{
+	  /* For a '%s' and '%ls' directive with a non-constant string,
+	     the minimum number of characters is the greater of WIDTH
+	     and either 0 in mode 1 or the smaller of PRECISION and 1
+	     in mode 2, and the maximum is PRECISION or -1 to disable
+	     tracking.  */
+
+	  if (0 <= prec)
+	    {
+	      if ((unsigned)prec < slen.range.min
+		  || slen.range.min >= HOST_WIDE_INT_MAX)
+		slen.range.min = prec;
+	      if ((unsigned)prec < slen.range.max
+		  || slen.range.max >= HOST_WIDE_INT_MAX)
+		slen.range.max = prec;
+	    }
+	  else if (slen.range.min >= HOST_WIDE_INT_MAX)
+	    {
+	      slen.range.min = max_bytes_for_unknown_str;
+	      slen.range.max = max_bytes_for_unknown_str;
+	    }
+
+	  res.range = slen.range;
+
+	  /* The output is considered bounded when a precision has been
+	     specified to limit the number of bytes or when the number
+	     of bytes is known or contrained to some range.  */
+	  res.bounded = 0 <= prec || slen.bounded;
+	  res.constant = false;
+	}
+    }
+
+  /* Adjust the lengths for field width.  */
+  if (res.range.min < width)
+    res.range.min = width;
+
+  if (res.range.max < width)
+    res.range.max = width;
+
+  /* Adjust BOUNDED if width happens to make them the same .  */
+  if (res.range.min == res.range.max && res.range.min < HOST_WIDE_INT_MAX)
+    res.bounded = true;
+
+  return res;
+}
+
+#pragma GCC diagnostic ignored "-Wsuggest-attribute=format"
+
+/* Compute the length of the output resulting from the conversion
+   specification SPEC with the argumet ARG a in a call described by INFO
+   and update the overall result of the call in *RES.  The format directive
+   corresponding to SPEC starts at CVTBEG and is CVTLEN characters long.  */
+
+static void
+format_directive (const pass_sprintf_length::call_info &info,
+		  format_result *res, const char *cvtbeg, size_t cvtlen,
+		  const conversion_spec &spec, tree arg)
+{
+  /* Offset of the beginning of the directive from the beginning
+     of the format string.  */
+  size_t offset = cvtbeg - info.fmtstr;
+
+  /* Create a location for the whole directive from the % to the format
+     specifier.  */
+  substring_loc dirloc (info.fmtloc, offset, offset, offset + cvtlen - 1);
+
+  /* Also create a location range for the argument if possible.
+     This doesn't work for integer literals or function calls.  */
+  source_range argrange;
+  source_range *pargrange;
+  if (arg && CAN_HAVE_LOCATION_P (arg))
+    {
+      argrange = EXPR_LOCATION_RANGE (arg);
+      pargrange = &argrange;
+    }
+  else
+    pargrange = NULL;
+
+  /* Bail when there is no function to compute the output length,
+     or when minimum length checking has been disabled.   */
+  if (!spec.fmtfunc || res->number_chars_min >= HOST_WIDE_INT_MAX)
+    return;
+
+  /* Compute the (approximate) length of the formatted output.  */
+  fmtresult fmtres = spec.fmtfunc (spec, arg);
+
+  /* The overall result is bounded only if the output of every
+     directive is exact or bounded.  */
+  res->bounded = res->bounded && fmtres.bounded;
+  res->constant = res->constant && fmtres.constant;
+
+  if (fmtres.range.max >= HOST_WIDE_INT_MAX)
+    {
+      /* 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.range.min >= HOST_WIDE_INT_MAX)
+    {
+      /* Disable exact length checking after a failure to determine
+	 even the minimum number of characters (it shouldn't happen
+	 except in an error) but keep tracking the minimum and maximum
+	 number of characters.  */
+      res->number_chars = -1;
+      return;
+    }
+
+  /* Compute the number of available bytes in the destination.  There
+     must always be at least one byte of space for the terminating
+     NUL that's appended after the format string has been processed.  */
+  unsigned HOST_WIDE_INT navail = min_bytes_remaining (info.objsize, *res);
+
+  if (fmtres.range.min < fmtres.range.max)
+    {
+      /* The result is a range (i.e., it's inexact).  */
+      if (!res->warned)
+	{
+	  bool warned = false;
+
+	  if (navail < fmtres.range.min)
+	    {
+	      if (fmtres.range.min == fmtres.range.max)
+		{
+		  const char* fmtstr
+		    = (info.bounded
+		       ? G_("%<%.*s%> directive output truncated writing "
+			    "%wu bytes into a region of size %wu")
+		       : G_("%<%.*s%> directive writing %wu bytes "
+			    "into a region of size %wu"));
+		  warned = fmtwarn (dirloc, pargrange, NULL,
+				    OPT_Wformat_length_, fmtstr,
+				    (int)cvtlen, cvtbeg, fmtres.range.min,
+				    navail);
+		}
+	      else
+		{
+		  const char* fmtstr
+		    = (info.bounded
+		       ? G_("%<%.*s%> directive output truncated writing "
+			    "between %wu and %wu bytes into a region of "
+			    "size %wu")
+		       : G_("%<%.*s%> directive writing between %wu and "
+			    "%wu bytes into a region of size %wu"));
+		  warned = fmtwarn (dirloc, pargrange, NULL,
+				    OPT_Wformat_length_, fmtstr,
+				    (int)cvtlen, cvtbeg,
+				    fmtres.range.min, fmtres.range.max, navail);
+		}
+	    }
+	  else if (navail < fmtres.range.max
+		   && (fmtres.bounded || 1 < warn_format_length))
+	    {
+	      if (fmtres.range.max >= HOST_WIDE_INT_MAX)
+		{
+		  const char* fmtstr
+		    = (info.bounded
+		       ? G_("%<%.*s%> directive output may be truncated writing "
+			    "%wu or more bytes a region of size %wu")
+		       : G_("%<%.*s%> directive writing %wu or more bytes "
+			    "into a region of size %wu"));
+		  warned = fmtwarn (dirloc, pargrange, NULL,
+				    OPT_Wformat_length_, fmtstr,
+				    (int)cvtlen, cvtbeg,
+				    fmtres.range.min, navail);
+		}
+	      else
+		{
+		  const char* fmtstr
+		    = (info.bounded
+		       ? G_("%<%.*s%> directive output may be truncated writing "
+			    "between %wu and %wu bytes into a region of size "
+			    "%wu")
+		       : G_("%<%.*s%> directive writing between %wu and %wu "
+			    "bytes into a region of size %wu"));
+		  warned = fmtwarn (dirloc, pargrange, NULL,
+				    OPT_Wformat_length_, fmtstr,
+				    (int)cvtlen, cvtbeg,
+				    fmtres.range.min, fmtres.range.max,
+				    navail);
+		}
+	    }
+
+	  res->warned |= warned;
+
+	  if (warned && fmtres.argmin)
+	    {
+	      if (fmtres.argmin == fmtres.argmax)
+		inform (info.fmtloc, "directive argument %qE", fmtres.argmin);
+	      else if (fmtres.bounded)
+		inform (info.fmtloc, "directive argument in the range [%E, %E]",
+			fmtres.argmin, fmtres.argmax);
+	      else
+		inform (info.fmtloc,
+			"using the range [%qE, %qE] for directive argument",
+			fmtres.argmin, fmtres.argmax);
+	    }
+	}
+
+      /* Disable exact length checking but adjust the minimum and maximum.  */
+      res->number_chars = -1;
+      if (res->number_chars_max < HOST_WIDE_INT_MAX
+	  && fmtres.range.max < HOST_WIDE_INT_MAX)
+	res->number_chars_max += fmtres.range.max;
+
+      res->number_chars_min += fmtres.range.min;
+    }
+  else
+    {
+      if (!res->warned && 0 < fmtres.range.min && navail < fmtres.range.min)
+	{
+	  const char* fmtstr
+	    = (info.bounded
+	       ? (1 < fmtres.range.min
+		  ? G_("%<%.*s%> directive output truncated while writing "
+		       "%wu bytes into a region of size %wu")
+		  : G_("%<%.*s%> directive output truncated while writing "
+		       "%wu byte into a region of size %wu"))
+	       : (1 < fmtres.range.min
+		  ? G_("%<%.*s%> directive writing %wu bytes "
+		       "into a region of size %wu")
+		  : G_("%<%.*s%> directive writing %wu byte "
+		       "into a region of size %wu")));
+
+	  res->warned = fmtwarn (dirloc, pargrange, NULL,
+				 OPT_Wformat_length_, fmtstr,
+				 (int)cvtlen, cvtbeg, fmtres.range.min,
+				 navail);
+	}
+      *res += fmtres.range.min;
+    }
+}
+
+/* Account for the number of bytes between BEG and END (or between
+   BEG + strlen (BEG) when END is null) in the format string in a call
+   to a formatted output function described by INFO.  Reflect the count
+   in RES and issue warnings as appropriate.  */
+
+static void
+add_bytes (const pass_sprintf_length::call_info &info,
+	   const char *beg, const char *end, format_result *res)
+{
+  if (res->number_chars_min >= HOST_WIDE_INT_MAX)
+    return;
+
+  /* The number of bytes to output is the number of bytes between
+     the end of the last directive and the beginning of the next
+     one if it exists, otherwise the number of characters remaining
+     in the format string plus 1 for the terminating NUL.  */
+  size_t nbytes = end ? end - beg : strlen (beg) + 1;
+
+  /* Return if there are no bytes to add at this time but there are
+     directives remaining in the format string.  */
+  if (!nbytes)
+    return;
+
+  /* Compute the range of available bytes in the destination.  There
+     must always be at least one byte left for the terminating NUL
+     that's appended after the format string has been processed.  */
+  result_range avail_range = bytes_remaining (info.objsize, *res);
+
+  /* If issuing a diagnostic (only when one hasn't already been issued),
+     distinguish between a possible overflow ("may write") and a certain
+     overflow somewhere "past the end."  (Ditto for truncation.)  */
+  if (!res->warned
+      && (avail_range.max < nbytes
+	  || ((res->bounded || 1 < warn_format_length)
+	      && avail_range.min < nbytes)))
+    {
+      /* Set NAVAIL to the number of available bytes used to decide
+	 whether or not to issue a warning below.  The exact kind of
+	 warning will depend on AVAIL_RANGE.  */
+      unsigned HOST_WIDE_INT navail = avail_range.max;
+      if (nbytes <= navail && avail_range.min < HOST_WIDE_INT_MAX
+	  && (res->bounded || 1 < warn_format_length))
+	navail = avail_range.min;
+
+      /* Compute the offset of the first format character that is beyond
+	 the end of the destination region and the length of the rest of
+	 the format string from that point on.  */
+      unsigned HOST_WIDE_INT off
+	= (unsigned HOST_WIDE_INT)(beg - info.fmtstr) + navail;
+
+      size_t len = strlen (info.fmtstr + off);
+
+      substring_loc loc
+	(info.fmtloc, off - !len, len ? off : 0, off + len - !!len);
+
+      /* Is the output of the last directive the result of the argument
+	 being within a range whose lower bound would fit in the buffer
+	 but the upper bound would not?  If so, use the word "may" to
+	 indicate that the overflow/truncation may (but need not) happen.  */
+      bool boundrange
+	= (res->number_chars_min < res->number_chars_max
+	   && res->number_chars_min < info.objsize);
+
+      res->warned = true;
+
+      if (!end && (nbytes - navail) == 1)
+	{
+	  /* There is room for the rest of the format string but none
+	     for the terminating nul. */
+	  const char *text
+	    = (info.bounded   // Snprintf and the like.
+	       ? (boundrange
+		  ? G_("output may be truncated before the last format character"
+		       : "output truncated before the last format character"))
+	       : (boundrange
+		  ? G_("may write a terminating nul past the end "
+		       "of the destination")
+		  : G_("writing a terminating nul past the end "
+		       "of the destination")));
+
+	  fmtwarn (loc, NULL, NULL, OPT_Wformat_length_, text);
+	}
+      else
+	{
+	  /* There isn't enough room for 1 or more characters that remain
+	     to copy from the format string. */
+	  const char *text
+	    = (info.bounded   // Snprintf and the like.
+	       ? (boundrange
+		  ? G_("output may be truncated at or before format character "
+		       "%qc at offset %wu")
+		  : G_("output truncated at format character %qc at offset %wu"))
+	       : (res->number_chars >= HOST_WIDE_INT_MAX
+		  ? G_("may write format character %#qc at offset %wu past "
+		       "the end of the destination")
+		  : G_("writing format character %#qc at offset %wu past "
+		       "the end of the destination")));
+
+	  fmtwarn (loc, NULL, NULL, OPT_Wformat_length_,
+	text, info.fmtstr[off], off);
+	}
+    }
+
+  if (res->warned && !end)
+    {
+      /* Help the user figure out how big a buffer they need.  */
+
+      location_t callloc = gimple_location (info.callstmt);
+
+      unsigned HOST_WIDE_INT min = res->number_chars_min;
+      unsigned HOST_WIDE_INT max = res->number_chars_max;
+      unsigned HOST_WIDE_INT exact
+	= (res->number_chars < HOST_WIDE_INT_MAX
+	   ? res->number_chars : res->number_chars_min);
+
+      if (min < max && max < HOST_WIDE_INT_MAX)
+	inform (callloc,
+		"format output between %wu and %wu bytes into "
+		"a destination of size %wu",
+		min + nbytes, max + nbytes, info.objsize);
+      else
+	inform (callloc,
+		(nbytes + exact == 1
+		 ? "format output %wu byte into a destination of size %wu"
+		 : "format output %wu bytes into a destination of size %wu"),
+		nbytes + exact, info.objsize);
+    }
+
+  *res += nbytes;
+}
+
+/* Compute the length of the output resulting from the call to a formatted
+   output function described by INFO and store the result of the call in
+   *RES.  Issue warnings for detected past the end writes.  */
+
+void
+pass_sprintf_length::compute_format_length (const call_info &info,
+					    format_result *res)
+{
+  /* The variadic argument counter.  */
+  unsigned argno = info.argidx;
+
+  /* Reset exact, minimum, and maximum character counters.  */
+  res->number_chars = res->number_chars_min = res->number_chars_max = 0;
+
+  /* No directive has been seen yet so the output is bounded and constant
+     until determined otherwise.  */
+  res->bounded = true;
+  res->constant = true;
+  res->warned = false;
+
+  const char *pf = info.fmtstr;
+
+  for ( ; ; )
+    {
+      /* The beginning of the next format directive.  */
+      const char *dir = strchr (pf, '%');
+
+      /* Add the number of bytes between the end of the last directive
+	 and either the next if one exists, or the end of the format
+	 string.  */
+      add_bytes (info, pf, dir, res);
+
+      if (!dir)
+	break;
+
+      pf = dir + 1;
+
+      if (0 && *pf == 0)
+	{
+	  /* Incomplete directive.  */
+	  return;
+	}
+
+      conversion_spec spec = conversion_spec ();
+
+      /* POSIX numbered argument index or zero when none.  */
+      unsigned dollar = 0;
+
+      if (ISDIGIT (*pf))
+	{
+	  /* This could be either a POSIX positional argument, the '0'
+	     flag, or a width, depending on what follows.  Store it as
+	     width and sort it out later after the next character has
+	     been seen.  */
+	  char *end;
+	  spec.width = strtol (pf, &end, 10);
+	  spec.have_width = true;
+	  pf = end;
+	}
+      else if ('*' == *pf)
+	{
+	  /* Similarly to the block above, this could be either a POSIX
+	     positional argument or a width, depending on what follows.  */
+	  if (argno < gimple_call_num_args (info.callstmt))
+	    spec.star_width = gimple_call_arg (info.callstmt, argno++);
+	  else
+	    return;
+	  ++pf;
+	}
+
+      if (*pf == '$')
+	{
+	  /* Handle the POSIX dollar sign which references the 1-based
+	     positional argument number.  */
+	  if (spec.have_width)
+	    dollar = spec.width + info.argidx;
+	  else if (spec.star_width
+		   && TREE_CODE (spec.star_width) == INTEGER_CST)
+	    dollar = spec.width + tree_to_shwi (spec.star_width);
+
+	  /* Bail when the numbered argument is out of range (it will
+	     have already been diagnosed by -Wformat).  */
+	  if (dollar == 0
+	      || dollar == info.argidx
+	      || dollar > gimple_call_num_args (info.callstmt))
+	    return;
+
+	  --dollar;
+
+	  spec.star_width = NULL_TREE;
+	  spec.have_width = false;
+	  ++pf;
+	}
+
+      if (dollar || !spec.star_width)
+	{
+	  if (spec.have_width && spec.width == 0)
+	    {
+	      /* The '0' that has been interpreted as a width above is
+		 actually a flag.  Reset HAVE_WIDTH, set the '0' flag,
+		 and continue processing other flags.  */
+	      spec.have_width = false;
+	      spec.set_flag ('0');
+	    }
+	  /* When either '$' has been seen, or width has not been seen,
+	     the next field is the optional flags followed by an optional
+	     width.  */
+	  for ( ; ; ) {
+	    switch (*pf)
+	      {
+	      case ' ':
+	      case '0':
+	      case '+':
+	      case '-':
+	      case '#':
+		spec.set_flag (*pf++);
+		break;
+
+	      default:
+		goto start_width;
+	      }
+	  }
+
+	start_width:
+	  if (ISDIGIT (*pf))
+	    {
+	      char *end;
+	      spec.width = strtol (pf, &end, 10);
+	      spec.have_width = true;
+	      pf = end;
+	    }
+	  else if ('*' == *pf)
+	    {
+	      spec.star_width = gimple_call_arg (info.callstmt, argno++);
+	      ++pf;
+	    }
+	  else if ('\'' == *pf)
+	    {
+	      /* The POSIX apostrophe indicating a numeric grouping
+		 in the current locale.  Even though it's possible to
+		 estimate the upper bound on the size of the output
+		 based on the number of digits it probably isn't worth
+		 continuing.  */
+	      return;
+	    }
+	}
+
+      if ('.' == *pf)
+	{
+	  ++pf;
+
+	  if (ISDIGIT (*pf))
+	    {
+	      char *end;
+	      spec.precision = strtol (pf, &end, 10);
+	      spec.have_precision = true;
+	      pf = end;
+	    }
+	  else if ('*' == *pf)
+	    {
+	      spec.star_precision = gimple_call_arg (info.callstmt, argno++);
+	      ++pf;
+	    }
+	  else
+	    return;
+	}
+
+      switch (*pf)
+	{
+	case 'h':
+	  if (pf[1] == 'h')
+	    {
+	      ++pf;
+	      spec.modifier = FMT_LEN_hh;
+	    }
+	  else
+	    spec.modifier = FMT_LEN_h;
+	  ++pf;
+	  break;
+
+	case 'j':
+	  spec.modifier = FMT_LEN_j;
+	  ++pf;
+	  break;
+
+	case 'L':
+	  spec.modifier = FMT_LEN_L;
+	  ++pf;
+	  break;
+
+	case 'l':
+	  if (pf[1] == 'l')
+	    {
+	      ++pf;
+	      spec.modifier = FMT_LEN_ll;
+	    }
+	  else
+	    spec.modifier = FMT_LEN_l;
+	  ++pf;
+	  break;
+
+	case 't':
+	  spec.modifier = FMT_LEN_t;
+	  ++pf;
+	  break;
+
+	case 'z':
+	  spec.modifier = FMT_LEN_z;
+	  ++pf;
+	  break;
+	}
+
+      switch (*pf)
+	{
+	  /* Handle a sole '%' character the same as "%%" but since it's
+	     undefined prevent the result from being folded.  */
+	case '\0':
+	  --pf;
+	  res->bounded = false;
+	case '%':
+	  spec.fmtfunc = format_percent;
+	  break;
+
+	case 'a':
+	case 'A':
+	case 'e':
+	case 'E':
+	case 'f':
+	case 'F':
+	case 'g':
+	case 'G':
+	  res->floating = true;
+	  spec.fmtfunc = format_floating;
+	  break;
+
+	case 'd':
+	case 'i':
+	case 'o':
+	case 'u':
+	case 'x':
+	case 'X':
+	  spec.fmtfunc = format_integer;
+	  break;
+
+	case 'p':
+	  spec.fmtfunc = format_pointer;
+	  break;
+
+	case 'n':
+	  return;
+
+	case 'c':
+	case 'S':
+	case 's':
+	  spec.fmtfunc = format_string;
+	  break;
+
+	default:
+	  return;
+	}
+
+      spec.specifier = *pf++;
+
+      /* Compute the length of the format directive.  */
+      size_t dirlen = pf - dir;
+
+      /* Extract the argument if the directive takes one and if it's
+	 available (e.g., the function doesn't take a va_list).  Treat
+	 missing arguments the same as va_list, even though they will
+	 have likely already been diagnosed by -Wformat.  */
+      tree arg = NULL_TREE;
+      if (spec.specifier != '%'
+	  && argno < gimple_call_num_args (info.callstmt))
+	arg = gimple_call_arg (info.callstmt, dollar ? dollar : argno++);
+
+      ::format_directive (info, res, dir, dirlen, spec, arg);
+    }
+}
+
+/* Return the size of the object referenced by the expression DEST if
+   available, or -1 otherwise.
+   Try to work around some of the limitations of __builtin_object_size
+   in the common simple case when DEST is a POINTER_PLUS_EXPR involving
+   an array.  */
+
+static unsigned HOST_WIDE_INT
+get_destination_size (tree dest)
+{
+  /* Try to use __builtin_object_size although it rarely returns
+     a useful result even for straighforward cases.  */
+  int ost = warn_format_length < 2 ? 0 : 2;
+  /* A valid __builtin_object_size result for OST of zero is less
+     than SIZE_MAX, and for OST of 2 is greater than zero.  */
+  unsigned HOST_WIDE_INT size = compute_builtin_object_size (dest, ost);
+  if ((!ost && size < HOST_WIDE_INT_M1U) || (ost && size))
+    return size;
+
+  /* If __builtin_object_size fails to deliver, try to compute
+     it for the very basic (but common) cases.  */
+  if (TREE_CODE (dest) == SSA_NAME
+      && POINTER_TYPE_P (TREE_TYPE (dest)))
+    {
+      gimple *def = SSA_NAME_DEF_STMT (dest);
+      if (gimple_code (def) == GIMPLE_ASSIGN)
+	{
+	  tree_code code = gimple_assign_rhs_code (def);
+	  if (code == POINTER_PLUS_EXPR)
+	    {
+	      tree off = gimple_assign_rhs2 (def);
+	      dest = gimple_assign_rhs1 (def);
+
+	      if (cst_and_fits_in_hwi (off))
+		{
+		  unsigned HOST_WIDE_INT size = get_destination_size (dest);
+		  if (size < HOST_WIDE_INT_M1U)
+		    return size - tree_to_shwi (off);
+		}
+	    }
+	}
+    }
+
+  return HOST_WIDE_INT_M1U;
+}
+
+/* Given a suitable result RES of a call to a formatted output function
+   described by INFO, substitute the result for the return value of
+   the call.  The result is suitable if the number of bytes it represents
+   is known and exact.  */
+
+static void
+try_substitute_return_value (gimple_stmt_iterator gsi,
+			     const pass_sprintf_length::call_info &info,
+			     const format_result &res)
+{
+  tree lhs = gimple_get_lhs (info.callstmt);
+  if (lhs && res.bounded && res.number_chars < HOST_WIDE_INT_MAX)
+    {
+      /* Replace the left-hand side of the call with the constant
+	 result of the formatting function minus 1 for the terminating
+	 NUL which the functions' return value does not include.  */
+      gimple_call_set_lhs (info.callstmt, NULL_TREE);
+      tree cst = build_int_cst (integer_type_node, res.number_chars - 1);
+      gimple *g = gimple_build_assign (lhs, cst);
+      gsi_insert_after (&gsi, g, GSI_NEW_STMT);
+      update_stmt (info.callstmt);
+
+      if (dump_file)
+	{
+	  location_t callloc = gimple_location (info.callstmt);
+	  fprintf (dump_file, "On line %i substituting ",
+		   LOCATION_LINE (callloc));
+	  print_generic_expr (dump_file, cst, dump_flags);
+	  fprintf (dump_file, " for ");
+	  print_generic_expr (dump_file, info.func, dump_flags);
+	  fprintf (dump_file, " return value (output %s).\n",
+		   res.constant ? "constant" : "variable");
+	}
+    }
+  else if (dump_file)
+    {
+      location_t callloc = gimple_location (info.callstmt);
+      fprintf (dump_file, "On line %i ", LOCATION_LINE (callloc));
+      print_generic_expr (dump_file, info.func, dump_flags);
+
+      const char *ign = lhs ? "" : " ignored";
+      if (res.number_chars >= HOST_WIDE_INT_MAX)
+	fprintf (dump_file, " return value in range [%lu, %lu]%s.\n",
+		 (unsigned long)res.number_chars_min,
+		 (unsigned long)res.number_chars_max, ign);
+      else
+	fprintf (dump_file, " return value %lu%s.\n",
+		 (unsigned long)res.number_chars, ign);
+    }
+}
+
+/* Determine if a GIMPLE CALL is one to one of the sprintf-like built-in
+   functions and if so, handle it.  */
+
+void
+pass_sprintf_length::handle_gimple_call (gimple_stmt_iterator gsi)
+{
+  call_info info = call_info ();
+
+  info.callstmt = gsi_stmt (gsi);
+  info.func = gimple_call_fn (info.callstmt);
+  if (!info.func)
+    return;
+
+  if (TREE_CODE (info.func) == ADDR_EXPR)
+    info.func = TREE_OPERAND (info.func, 0);
+
+  if (TREE_CODE (info.func) != FUNCTION_DECL
+      || !DECL_BUILT_IN(info.func)
+      || DECL_BUILT_IN_CLASS (info.func) != BUILT_IN_NORMAL)
+    return;
+
+  info.fncode = DECL_FUNCTION_CODE (info.func);
+
+  /* The size of the destination as in snprintf(dest, size, ...).  */
+  unsigned HOST_WIDE_INT dstsize = HOST_WIDE_INT_M1U;
+
+  /* The size of the destination determined by __builtin_object_size.  */
+  unsigned HOST_WIDE_INT objsize = HOST_WIDE_INT_M1U;
+
+  /* Buffer size argument number (snprintf and vsnprintf).  */
+  unsigned HOST_WIDE_INT idx_dstsize = HOST_WIDE_INT_M1U;
+
+  /* Object size argument number (snprintf_chk and vsnprintf_chk).  */
+  unsigned HOST_WIDE_INT idx_objsize = HOST_WIDE_INT_M1U;
+
+  /* Format string argument number (valid for all functions).  */
+  unsigned idx_format;
+
+  switch (info.fncode)
+    {
+    case BUILT_IN_SPRINTF:
+      // Signature:
+      //   __builtin_sprintf (dst, format, ...)
+      idx_format = 1;
+      info.argidx = 2;
+      break;
+
+    case BUILT_IN_SNPRINTF:
+      // Signature:
+      //   __builtin_snprintf (dst, size, format, ...)
+      idx_dstsize = 1;
+      idx_format = 2;
+      info.argidx = 3;
+      info.bounded = true;
+      break;
+
+    case BUILT_IN_SNPRINTF_CHK:
+      // Signature:
+      //   __builtin___sprintf_chk (dst, size, ost, objsize, format, ...)
+      idx_dstsize = 1;
+      idx_objsize = 3;
+      idx_format = 4;
+      info.argidx = 5;
+      info.bounded = true;
+      break;
+
+    case BUILT_IN_SPRINTF_CHK:
+      // Signature:
+      //   __builtin___sprintf_chk (dst, ost, objsize, format, ...)
+      idx_objsize = 2;
+      idx_format = 3;
+      info.argidx = 4;
+      break;
+
+    case BUILT_IN_VSNPRINTF:
+      // Signature:
+      //   __builtin_vsprintf (dst, size, format, va)
+      idx_dstsize = 1;
+      idx_format = 2;
+      info.argidx = -1;
+      info.bounded = true;
+      break;
+
+    case BUILT_IN_VSNPRINTF_CHK:
+      // Signature:
+      //   __builtin___vsnprintf_chk (dst, size, ost, objsize, format, va)
+      idx_dstsize = 1;
+      idx_objsize = 2;
+      idx_format = 3;
+      info.argidx = -1;
+      info.bounded = true;
+      break;
+
+    case BUILT_IN_VSPRINTF:
+      // Signature:
+      //   __builtin_vsprintf (dst, format, va)
+      idx_format = 1;
+      info.argidx = -1;
+      break;
+
+    case BUILT_IN_VSPRINTF_CHK:
+      // Signature:
+      //   __builtin___vsprintf_chk (dst, ost, objsize, format, va)
+      idx_format = 3;
+      idx_objsize = 2;
+      info.argidx = -1;
+      break;
+
+    default:
+      return;
+    }
+
+  info.format = gimple_call_arg (info.callstmt, idx_format);
+
+  if (idx_dstsize == HOST_WIDE_INT_M1U)
+    {
+      // For non-bounded functions like sprintf, to to determine
+      // the size of the destination from the object or pointer
+      // passed to it as the first argument.
+      dstsize = get_destination_size (gimple_call_arg (info.callstmt, 0));
+    }
+  else if (tree size = gimple_call_arg (info.callstmt, idx_dstsize))
+    {
+      /* For bounded functions try to get the size argument.  */
+
+      if (TREE_CODE (size) == INTEGER_CST)
+	{
+	  dstsize = tree_to_uhwi (size);
+	  /* No object can be larger than HOST_WIDE_INT_MAX bytes
+	     (half the address space).  This imposes a limit that's
+	     one less than that.  */
+	  if (dstsize >= HOST_WIDE_INT_MAX)
+	    warning_at (gimple_location (info.callstmt), 0,
+			"specified destination size %wu too large",
+			dstsize);
+	}
+      else if (TREE_CODE (size) == SSA_NAME)
+	{
+	  /* Try to determine the range of values of the argument
+	     and use the greater of the two at -Wformat-level 1 and
+	     the smaller of them at level 2.  */
+	  wide_int min, max;
+	  enum value_range_type range_type
+	    = get_range_info (size, &min, &max);
+	  if (range_type == VR_RANGE)
+	    {
+	      dstsize
+		= (warn_format_length < 2
+		   ? wi::fits_uhwi_p (max) ? max.to_uhwi () : max.to_shwi ()
+		   : wi::fits_uhwi_p (min) ? min.to_uhwi () : min.to_shwi ());
+	    }
+	}
+    }
+
+  if (idx_objsize != HOST_WIDE_INT_M1U)
+    {
+      if (tree size = gimple_call_arg (info.callstmt, idx_objsize))
+	  if (tree_fits_uhwi_p (size))
+	    objsize = tree_to_uhwi (size);
+    }
+
+  if (info.bounded && !dstsize)
+    {
+      /* As a special case, when the explicitly specified destination
+	 size argument (to a bounded function like snprintf) is zero
+	 it is a request to determine the number of bytes on output
+	 without actually producing any.  Pretend the size is
+	 unlimited in this case.  */
+      info.objsize = HOST_WIDE_INT_MAX;
+    }
+  else
+    {
+      /* Set the object size to the smaller of the two arguments
+	 of both have been specified and they're not equal.  */
+      info.objsize = dstsize < objsize ? dstsize : objsize;
+
+      if (info.bounded
+	  && dstsize != HOST_WIDE_INT_M1U && objsize < dstsize)
+	{
+	  warning_at (gimple_location (info.callstmt), 0,
+		      "specified size %wu exceeds the size %wu "
+		      "of the destination object", dstsize, objsize);
+	}
+    }
+
+  if (integer_zerop (info.format))
+    {
+      /* This is diagnosed with -Wformat only when the null is
+	 a constant pointer.  The warning here diagnoses instances
+	 where the pointer is not constant.  */
+      warning_at (EXPR_LOC_OR_LOC (info.format, input_location),
+		  OPT_Wformat_length_, "null format string");
+      return;
+    }
+
+  info.fmtstr = get_format_string (info.format, &info.fmtloc);
+  if (!info.fmtstr)
+    return;
+
+  /* The result is the number of bytes output by the formatting
+     function, including the terminating NUL.  */
+  format_result res;
+  compute_format_length (info, &res);
+
+  /* When optimizing and the printf return value optimization is enabled,
+     attempt to substitute the computed result for the return value of
+     the call.  Avoid this optimization when -frounding-math is in effect
+     and the format string contains a floating point directive.  */
+  if (0 < optimize && flag_printf_return_value
+      && (!flag_rounding_math || !res.floating))
+    try_substitute_return_value (gsi, info, res);
+}
+
+unsigned int
+pass_sprintf_length::execute (function *fun)
+{
+  basic_block bb;
+  FOR_EACH_BB_FN (bb, fun)
+    {
+      for (gimple_stmt_iterator si = gsi_start_bb (bb); !gsi_end_p (si);
+	   gsi_next (&si))
+	{
+	  /* Iterate over statements, looking for function calls.  */
+	  gimple *stmt = gsi_stmt (si);
+
+	  if (gimple_code (stmt) == GIMPLE_CALL)
+	    handle_gimple_call (si);
+	}
+    }
+
+  return 0;
+}
+
+}   /* Unnamed namespace.  */
+
+gimple_opt_pass *
+make_pass_sprintf_length (gcc::context *ctxt)
+{
+  return new pass_sprintf_length (ctxt);
+}
diff --git a/gcc/passes.def b/gcc/passes.def
index 3647e90..ac954cd 100644
--- a/gcc/passes.def
+++ b/gcc/passes.def
@@ -43,6 +43,7 @@  along with GCC; see the file COPYING3.  If not see
   NEXT_PASS (pass_warn_function_return);
   NEXT_PASS (pass_expand_omp);
   NEXT_PASS (pass_build_cgraph_edges);
+  NEXT_PASS (pass_sprintf_length, false);
   TERMINATE_PASS_LIST (all_lowering_passes)
 
   /* Interprocedural optimization passes.  */
@@ -303,6 +304,7 @@  along with GCC; see the file COPYING3.  If not see
       NEXT_PASS (pass_simduid_cleanup);
       NEXT_PASS (pass_lower_vector_ssa);
       NEXT_PASS (pass_cse_reciprocals);
+      NEXT_PASS (pass_sprintf_length, true);
       NEXT_PASS (pass_reassoc, false /* insert_powi_p */);
       NEXT_PASS (pass_strength_reduction);
       NEXT_PASS (pass_split_paths);
diff --git a/gcc/print-tree.c b/gcc/print-tree.c
index 468f1ff..d69bf2a 100644
diff --git a/gcc/targhooks.c b/gcc/targhooks.c
index 69037c1..5b967f7 100644
--- a/gcc/targhooks.c
+++ b/gcc/targhooks.c
@@ -1421,6 +1421,55 @@  no_c99_libc_has_function (enum function_class fn_class ATTRIBUTE_UNUSED)
   return false;
 }
 
+/* Return the MPFR rounding specifier character corresponding to
+   the default printf rounding behavior or -1 for don't care.  */
+
+int
+default_libc_printf_round_mode (void)
+{
+  /* N for round to nearest.  This is the IEEE 754 default rounding
+     mode used by, for example, Glibc and Solaris libc.  */
+  return 'N';
+}
+
+/* Return the format string to which "%p" corresponds.  By default,
+   assume it corresponds to the C99 "%zx" format and set *FLAGS to
+   the empty string to indicate that format flags have no effect.
+   An example of an implementation that matches this description
+   is AIX.  */
+
+const char*
+default_libc_printf_pointer_format (tree, const char **flags)
+{
+  *flags = "";
+
+  return "%zx";
+}
+
+/* Glibc formats pointers as if by "%zx" except for the null pointer
+   which outputs "(nil)".  It ignores the pound ('#') format flag but
+   interprets the space and plus flags the same as in the integer
+   directive.  */
+
+const char*
+gnu_libc_printf_pointer_format (tree arg, const char **flags)
+{
+  *flags = " +";
+
+  return arg && integer_zerop (arg) ? "(nil)" : "%#zx";
+}
+
+/* Solaris libc formats pointers as if by "%zx" with the pound ('#')
+   format flag having the same meaning as in the integer directive.  */
+
+const char*
+solaris_libc_printf_pointer_format (tree, const char **flags)
+{
+  *flags = "#";
+
+  return "%zx";
+}
+
 tree
 default_builtin_tm_load_store (tree ARG_UNUSED (type))
 {
diff --git a/gcc/targhooks.h b/gcc/targhooks.h
index 2e7ca72..d99ad9e 100644
--- a/gcc/targhooks.h
+++ b/gcc/targhooks.h
@@ -190,6 +190,11 @@  extern bool default_libc_has_function (enum function_class);
 extern bool no_c99_libc_has_function (enum function_class);
 extern bool gnu_libc_has_function (enum function_class);
 
+extern int default_libc_printf_round_mode (void);
+extern const char* default_libc_printf_pointer_format (tree, const char **);
+extern const char* gnu_libc_printf_pointer_format (tree, const char **);
+extern const char* solaris_libc_printf_pointer_format (tree, const char **);
+
 extern tree default_builtin_tm_load_store (tree);
 
 extern int default_memory_move_cost (machine_mode, reg_class_t, bool);
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c
new file mode 100644
index 0000000..e66f3ae
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c
@@ -0,0 +1,1373 @@ 
+/* { dg-do compile } */
+/* { dg-options "-std=c99 -Wformat -Wformat-length=1 -ftrack-macro-expansion=0" } */
+
+/* When debugging, define LINE to the line number of the test case to exercise
+   and avoid exercising any of the others.  The buffer and objsize macros
+   below make use of LINE to avoid warnings for other lines.  */
+#ifndef LINE
+# define LINE 0
+#endif
+
+char buffer [256];
+extern char *ptr;
+
+/* Evaluate to an array of SIZE characters when non-negative and LINE
+   is not set or set to the line the macro is on, or to a pointer to
+   an unknown object otherwise.  */
+#define buffer(size)							\
+  (0 <= size && (!LINE || __LINE__ == LINE)				\
+   ? buffer + sizeof buffer - size : ptr)
+
+/* Evaluate to SIZE when non-negative and LINE is not set or set to
+   the line the macro is on, or to SIZE_MAX otherise.  */
+#define objsize(size)							\
+  (0 <= size && (!LINE || __LINE__ == LINE)				\
+   ? size : __SIZE_MAX__)
+
+typedef __SIZE_TYPE__ size_t;
+
+#if !__cplusplus
+typedef __WCHAR_TYPE__ wchar_t;
+#endif
+
+typedef unsigned char UChar;
+
+const char s0[] = "";
+const char s1[] = "1";
+const char s2[] = "12";
+const char s3[] = "123";
+const char s4[] = "1234";
+const char s5[] = "12345";
+const char s6[] = "123456";
+const char s7[] = "1234567";
+const char s8[] = "12345678";
+
+void sink (void*, ...);
+
+/* Macro to verify that calls to __builtin_sprintf (i.e., with no size
+   argument) issue diagnostics by correctly determining the size of
+   the destination buffer.  */
+#define T(size, fmt, ...)						\
+  __builtin_sprintf (buffer (size), fmt, __VA_ARGS__),			\
+    sink (buffer, ptr);
+
+/* Exercise the "%c" and "%lc" directive with constant arguments.  */
+
+void test_sprintf_c_const (void)
+{
+  T (-1, "%c",    0);           /* No warning for unknown destination size.  */
+  T (0, "%c",     0);           /* { dg-warning ".%c. directive writing 1 byte into a region of size 0" } */
+  T (1, "%c",     0);           /* { dg-warning "writing a terminating nul past the end" } */
+  T (1, "%c",   '1');           /* { dg-warning "nul past the end" } */
+  T (2, "%c",   '1');
+  T (2, "%2c",  '1');           /* { dg-warning "nul past the end" } */
+  T (2, "%3c",  '1');           /* { dg-warning "into a region" } */
+  T (2, "%c%c", '1', '2');      /* { dg-warning "nul past the end" } */
+  T (3, "%c%c", '1', '2');
+
+  T (2, "%1$c%2$c", '1', '2');  /* { dg-warning "does not support %n.|nul past the end" } */
+  T (3, "%1$c%2$c", '1', '2');
+}
+
+/* Exercise the "%p" directive with constant arguments.  */
+
+void test_sprintf_p_const (void)
+{
+  /* GLIBC and uClibc format null pointers as "(nil)".  Sane implementations
+     format null pointers as 0 or 0x0 and so the following will only be
+     diagnosed on the former targets.  */
+  T (5, "%p",     (void*)0);
+  /* { dg-warning "nul past the end" "(nil)" { target *-linux-gnu *-*-uclinux } 79 } */
+
+  /* The exact output for %p is unspecified by C.  Two formats are known:
+     same as %tx (for example AIX) and same as %#tx (for example Solaris).  */
+  T (0, "%p",     (void*)0x1);    /* { dg-warning ".%p. directive writing . bytes into a region of size 0" } */
+  T (1, "%p",     (void*)0x12);   /* { dg-warning ".%p. directive writing . bytes into a region of size 1" } */
+  T (2, "%p",     (void*)0x123);  /* { dg-warning ".%p. directive writing . bytes into a region of size 2" } */
+
+  /* GLIBC and uClibc treat the ' ' flag with the "%p" directive the same
+     as with signed integer conversions (i.e., it prepends a space).  Other
+     known implementations ignore it.  */
+  T (6, "% p",    (void*)0x234);  /* { dg-warning ". . flag used with .%p." } */
+  /* { dg-warning "nul past the end" "Glibc %p" { target *-linux-gnu } 91 } */
+  /* { dg-warning "nul past the end" "Generic %p" { target *-*-uclinux } 91 } */
+}
+
+/* Verify that no warning is issued for calls that write into a flexible
+   array member whose size isn't known.  Also verify that calls that use
+   a flexible array member as an argument to the "%s" directive do not
+   cause a warning.  */
+
+void test_sprintf_flexarray (void *p, int i)
+{
+  struct S
+  {
+    int n;
+    char a [];
+  } *s = p;
+
+  __builtin_sprintf (s->a, "%c",       'x');
+
+  __builtin_sprintf (s->a, "%s",       "");
+  __builtin_sprintf (s->a, "%s",       "abc");
+  __builtin_sprintf (s->a, "abc%sghi", "def");
+
+  __builtin_sprintf (s->a, "%i",       1234);
+
+  __builtin_sprintf (buffer (1), "%s",  s->a);
+  __builtin_sprintf (buffer (1), "%s",  s [i].a);
+}
+
+/* Verify that the note printed along with the diagnostic mentions
+   the correct sizes and refers to the location corresponding to
+   the affected directive.  */
+
+void test_sprintf_note (void)
+{
+#define P __builtin_sprintf
+
+  /* Diagnostic column numbers are 1-based.  */
+
+  P (buffer (0),                /* { dg-message "format output 4 bytes into a destination of size 0" } */
+     "%c%s%i", '1', "2", 3);    /* { dg-warning "7:.%c. directive writing 1 byte into a region of size 0" } */
+
+  P (buffer (1),                /* { dg-message "format output 6 bytes into a destination of size 1" } */
+     "%c%s%i", '1', "23", 45);  /* { dg-warning "9:.%s. directive writing 2 bytes into a region of size 0" } */
+
+  P (buffer (2),                /* { dg-message "format output 6 bytes into a destination of size 2" } */
+     "%c%s%i", '1', "2", 345);  /* { dg-warning "11:.%i. directive writing 3 bytes into a region of size 0" } */
+
+  /* It would be nice if the caret in the location range for the format
+     string below could be made to point at the closing quote of the format
+     string, like so:
+       sprintf (d, "%c%s%i", '1', "2", 3456);
+                    ~~~~~~^
+     Unfortunately, that doesn't work with the current setup.  */
+  P (buffer (6),                /* { dg-message "format output 7 bytes into a destination of size 6" } */
+     "%c%s%i", '1', "2", 3456); /* { dg-warning "writing a terminating nul past the end of the destination" } */
+}
+
+#undef T
+#define T(size, fmt, ...)					  \
+  __builtin___sprintf_chk (buffer (size), 0, objsize (size), fmt, \
+			   __VA_ARGS__), sink (buffer, ptr)
+
+/* Exercise the "%c" and "%lc" directive with constant arguments.  */
+
+void test_sprintf_chk_c_const (void)
+{
+  T (-1, "%c",    0);            /* No warning for unknown destination size.  */
+  /* Verify the full text of the diagnostic for just the distinct messages
+     and use abbreviations in subsequent test cases.  */
+  T (0, "%c",     0);            /* { dg-warning ".%c. directive writing 1 byte into a region of size 0" } */
+  T (1, "%c",     0);            /* { dg-warning "writing a terminating nul past the end" } */
+  T (1, "%c",   '1');            /* { dg-warning "nul past the end" } */
+  T (2, "%c",   '1');
+  T (2, "%2c",  '1');            /* { dg-warning "nul past the end" } */
+  T (2, "%3c",  '1');            /* { dg-warning "into a region" } */
+  T (2, "%c%c", '1', '2');       /* { dg-warning "nul past the end" } */
+  T (3, "%c%c", '1', '2');
+
+  /* Wide characters.  */
+  T (0, "%lc",     0);           /* { dg-warning "nul past the end" } */
+  T (1, "%lc",     0);
+  T (1, "%lc%lc",  0, 0);
+  T (2, "%lc",     0);
+  T (2, "%lc%lc",  0, 0);
+
+  /* The following could result in as few as no bytes and in as many as
+     MB_CUR_MAX, but since the MB_CUR_MAX value is a runtime property
+     the write cannot be reliably diagnosed.  */
+  T (2, "%lc",  L'1');
+  T (2, "%1lc", L'1');
+  /* Writing some unknown number of bytes into a field two characters wide.  */
+  T (2, "%2lc", L'1');          /* { dg-warning "nul past the end" } */
+
+  T (3, "%lc%c",   L'1', '2');
+  /* Here in the best case each argument will format as single character,
+     causing the terminating NUL to be written past the end.  */
+  T (3, "%lc%c%c", L'1', '2', '3');   /* { dg-warning "nul past the end" } */
+  T (3, "%lc%lc%c", L'1', L'2', '3'); /* { dg-warning "nul past the end" } */
+}
+
+/* Exercise the "%s" and "%ls" directive with constant arguments.  */
+
+void test_sprintf_chk_s_const (void)
+{
+  T (-1, "%*s",  0, "");        /* No warning for unknown destination size.  */
+  T ( 0, "%*s",  0, "");        /* { dg-warning "nul past the end" } */
+  T ( 0, "%*s",  0, s0);        /* { dg-warning "nul past the end" } */
+  T ( 1, "%*s",  0, "");
+  T ( 1, "%*s",  0, s0);
+  T ( 1, "%*s",  0, "\0");
+  T ( 1, "%*s",  0, "1");       /* { dg-warning "nul past the end" } */
+  T ( 1, "%*s",  0, s1);        /* { dg-warning "nul past the end" } */
+  T ( 1, "%1s",     "");        /* { dg-warning "nul past the end" } */
+  T ( 1, "%1s",     s0);        /* { dg-warning "nul past the end" } */
+  T (-1, "%1s",    "1");        /* No warning for unknown destination size.  */
+  T ( 1, "%*s",  1, "");        /* { dg-warning "nul past the end" } */
+  T ( 1, "%*s",  1, s0);        /* { dg-warning "nul past the end" } */
+  T (-1, "%*s",  1, s0);        /* No warning for unknown destination size.  */
+
+  T (1, "%.0s",    "123");
+  T (1, "%.0s",    s3);
+  T (1, "%.*s", 0, "123");
+  T (1, "%.*s", 0, s3);
+  T (1, "%.1s",    "123");      /* { dg-warning "nul past the end" } */
+  T (1, "%.1s",    s3);         /* { dg-warning "nul past the end" } */
+  T (1, "%.*s", 1, "123");      /* { dg-warning "nul past the end" } */
+  T (1, "%.*s", 1, s3);         /* { dg-warning "nul past the end" } */
+
+  T (2, "%.*s", 0, "");
+  T (2, "%.*s", 0, "1");
+  T (2, "%.*s", 0, s1);
+  T (2, "%.*s", 0, "1\0");
+  T (2, "%.*s", 0, "12");
+  T (2, "%.*s", 0, s2);
+
+  T (2, "%.*s", 1, "");
+  T (2, "%.*s", 1, "1");
+  T (2, "%.*s", 1, s1);
+  T (2, "%.*s", 1, "1\0");
+  T (2, "%.*s", 1, "12");
+  T (2, "%.*s", 1, s2);
+
+  T (2, "%.*s", 2, "");
+  T (2, "%.*s", 2, "1");
+  T (2, "%.*s", 2, s1);
+  T (2, "%.*s", 2, "1\0");
+  T (2, "%.*s", 2, "12");       /* { dg-warning "nul past the end" } */
+  T (2, "%.*s", 2, s2);         /* { dg-warning "nul past the end" } */
+
+  T (2, "%.*s", 3, "");
+  T (2, "%.*s", 3, "1");
+  T (2, "%.*s", 3, s1);
+  T (2, "%.*s", 3, "1\0");
+  T (2, "%.*s", 3, "12");       /* { dg-warning "nul past the end" } */
+  T (2, "%.*s", 3, "123");      /* { dg-warning "into a region" } */
+  T (2, "%.*s", 3, s2);         /* { dg-warning "nul past the end" } */
+  T (2, "%.*s", 3, s3);         /* { dg-warning "into a region" } */
+
+  T (2, "%*s",  0, "");
+  T (2, "%*s",  0, "1");
+  T (2, "%*s",  0, s1);
+  T (2, "%*s",  0, "1\0");
+  T (2, "%*s",  0, "12");       /* { dg-warning "nul past the end" } */
+  T (2, "%*s",  0, s2);         /* { dg-warning "nul past the end" } */
+
+  /* Multiple directives.  */
+
+  T (1, "%s%s", "", "");
+  T (1, "%s%s", s0, s0);
+  T (1, "%s%s", "", "1");       /* { dg-warning "nul past the end" } */
+  T (1, "%s%s", s0, s1);        /* { dg-warning "nul past the end" } */
+  T (1, "%s%s", "1", "");       /* { dg-warning "nul past the end" } */
+  T (1, "%s%s", s1, s0);        /* { dg-warning "nul past the end" } */
+  T (1, "%s%s", "1", "2");      /* { dg-warning "into a region" } */
+  T (1, "%s%s", s1, s1);        /* { dg-warning "into a region" } */
+
+  T (2, "%s%s", "", "");
+  T (2, "%s%s", "", "1");
+  T (2, "%s%s", "1", "");
+  T (2, "%s%s", "", "12");      /* { dg-warning "nul past the end" } */
+  T (2, "%s%s", "1", "2");      /* { dg-warning "nul past the end" } */
+  T (2, "%s%s", "12", "2");     /* { dg-warning "into a region" } */
+  T (2, "%s%s", "1", "23");     /* { dg-warning "into a region" } */
+  T (2, "%s%s", "12", "3");     /* { dg-warning "into a region" } */
+  T (2, "%s%s", "12", "34");    /* { dg-warning "into a region" } */
+
+  T (2, "_%s",   "");
+  T (2, "%%%s",  "");
+  T (2, "%s%%",  "");
+  T (2, "_%s",   "1");          /* { dg-warning "nul past the end" } */
+  T (2, "%%%s",  "1");          /* { dg-warning "nul past the end" } */
+  T (2, "%s%%",  "1");          /* { dg-warning "nul past the end" } */
+  T (2, "_%s",   "12");         /* { dg-warning "into a region" } */
+  T (2, "__%s",  "1");          /* { dg-warning "into a region" } */
+
+  T (2, "%1$s%2$s", "12", "3"); /* { dg-warning ".%2.s. directive writing 1 byte into a region of size 0" } */
+  T (2, "%1$s%1$s", "12");      /* { dg-warning "does not support|.%1.s. directive writing 2 bytes into a region of size 0" } */
+  T (2, "%2$s%1$s", "1", "23"); /* { dg-warning ".%1.s. directive writing 1 byte into a region of size 0" } */
+  T (2, "%2$s%2$s", "1", "23"); /* { dg-warning "unused|%2.s. directive writing 2 bytes into a region of size 0" } */
+
+  T (3, "__%s", "");
+  T (3, "__%s", "1");           /* { dg-warning "nul past the end" } */
+  T (3, "%s_%s", "", "");
+  T (3, "%s_%s", "1", "");
+  T (3, "%s_%s", "", "1");
+  T (3, "%s_%s", "1", "2");     /* { dg-warning "nul past the end" } */
+
+  /* Wide strings.  */
+  T (-1, "%ls",      L"");
+  T ( 0, "%ls",      L"");      /* { dg-warning "nul past the end" } */
+  T ( 1, "%ls",      L"");
+  T ( 1, "%ls",      L"\0");
+  T ( 1, "%1ls",     L"");      /* { dg-warning "nul past the end" } */
+
+  T (0, "%*ls",  0, L"");       /* { dg-warning "nul past the end" } */
+  T (1, "%*ls",  0, L"");
+  T (1, "%*ls",  0, L"\0");
+  T (1, "%*ls",  1, L"");       /* { dg-warning "nul past the end" } */
+
+  T (1, "%ls",      L"1");      /* { dg-warning "nul past the end" } */
+  T (1, "%.0ls",    L"1");
+  T (2, "%.0ls",    L"1");
+  T (2, "%.1ls",    L"1");
+  T (2, "%.*ls", 1, L"1");
+
+  /* The "%.2ls" directive below will write at a minimum 1 byte (because
+     L"1" is known and can be assumed to convert to at least one multibyte
+     character), and at most 2 bytes because of the precision.  Since its
+     output is explicitly bounded it is diagnosed.  */
+  T (2, "%.2ls",    L"1");      /* { dg-warning "nul past the end" } */
+  T (2, "%.*ls", 2, L"1");      /* { dg-warning "nul past the end" } */
+
+  T (3, "%.0ls",    L"1");
+  T (3, "%.1ls",    L"1");
+  T (3, "%.2ls",    L"1");
+}
+
+/* Exercise the "%hhd", "%hhi", "%hho", "%hhu", and "%hhx" directives
+   with constant arguments.  */
+
+void test_sprintf_chk_hh_const (void)
+{
+  T (-1, "%hhd",        0);
+
+  T (1, "%hhd",         0);     /* { dg-warning "nul past the end" } */
+  T (1, "%hhd",         1);     /* { dg-warning "nul past the end" } */
+  T (1, "%hhd",        -1);     /* { dg-warning "into a region" } */
+  T (1, "%+hhd",        0);     /* { dg-warning "into a region" } */
+  T (1, "%+hhd",        1);     /* { dg-warning "into a region" } */
+  T (1, "%-hhd",        0);     /* { dg-warning "nul past the end" } */
+
+  T (1, "%hhi",         0);     /* { dg-warning "nul past the end" } */
+  T (1, "%hhi",         1);     /* { dg-warning "nul past the end" } */
+  T (1, "%hhi",        -1);     /* { dg-warning "into a region" } */
+  T (1, "%+hhi",        0);     /* { dg-warning "into a region" } */
+  T (1, "%+hhi",        1);     /* { dg-warning "into a region" } */
+  T (1, "%-hhi",        0);     /* { dg-warning "nul past the end" } */
+
+  T (2, "%hhi",         0);
+  T (2, "%hhi",         1);
+  T (2, "%hhi",         9);
+  T (2, "% hhi",        9);     /* { dg-warning "nul past the end" } */
+  T (2, "%+hhi",        9);     /* { dg-warning "nul past the end" } */
+  T (2, "%-hhi",        9);
+  T (2, "%hhi",        10);     /* { dg-warning "nul past the end" } */
+  T (2, "%hhi",        -1);     /* { dg-warning "nul past the end" } */
+  T (2, "% hhi",       -1);     /* { dg-warning "nul past the end" } */
+  T (2, "%+hhi",       -1);     /* { dg-warning "nul past the end" } */
+  T (2, "%-hhi",       -1);     /* { dg-warning "nul past the end" } */
+
+  T (2, "%hho",         0);
+  T (2, "%hho",         1);
+  T (2, "%hho",         7);
+  T (2, "%hho",       010);     /* { dg-warning "nul past the end" } */
+  T (2, "%hho",       077);     /* { dg-warning "nul past the end" } */
+  T (2, "%hho",        -1);     /* { dg-warning "into a region" } */
+
+  T (2, "%hhx",         0);
+  T (2, "%hhX",         1);
+  T (2, "%hhx",         7);
+  T (2, "%hhX",         8);
+  T (2, "%hhx",        -1);     /* { dg-warning "nul past the end" } */
+  T (2, "%hhX",       0xf);
+  T (2, "%hhx",      0x10);     /* { dg-warning "nul past the end" } */
+  T (2, "%hhX",      0xff);     /* { dg-warning "nul past the end" } */
+
+  T (1, "%#hhx",        0);     /* { dg-warning "nul past the end" } */
+  T (2, "%#hhx",        0);
+  T (3, "%#hhx",        1);     /* { dg-warning "nul past the end" } */
+
+  T (4, "%hhd",       255);
+  T (4, "%hhd",       256);
+  T (4, "%hhd",     0xfff);
+  T (4, "%hhd",    0xffff);
+
+  T (4, "%hhi",       255);
+  T (4, "%hhi",       256);
+  T (4, "%hhi",     0xfff);
+  T (4, "%hhi",    0xffff);
+
+  T (4, "%hhu",        -1);
+  T (4, "%hhu",       255);
+  T (4, "%hhu",       256);
+  T (4, "%hhu",     0xfff);
+  T (4, "%hhu",    0xffff);
+
+  T (4, "%#hhx",        0);
+  T (4, "%#hhx",        1);
+  T (4, "%#hhx",       -1);     /* { dg-warning "nul past the end" } */
+  T (4, "%#hhx",      0xf);
+  T (4, "%#hhx",     0x10);     /* { dg-warning "nul past the end" } */
+  T (4, "%#hhx",     0xff);     /* { dg-warning "nul past the end" } */
+  T (4, "%#hhx",    0xfff);     /* { dg-warning "nul past the end" } */
+
+  T (4, "%hhi %hhi",  0,  0);
+  T (4, "%hhi %hhi",  9,  9);
+  T (4, "%hhi %hhi",  1, 10);   /* { dg-warning "nul past the end" } */
+  T (4, "%hhi %hhi", 10,  1);   /* { dg-warning "nul past the end" } */
+  T (4, "%hhi %hhi", 11, 12);   /* { dg-warning "into a region" } */
+
+  T (5, "%0*hhd %0*hhi", 0,  7, 0,   9);
+  T (5, "%0*hhd %0*hhi", 1,  7, 1,   9);
+  T (5, "%0*hhd %0*hhi", 1,  7, 2,   9);
+  T (5, "%0*hhd %0*hhi", 2,  7, 1,   9);
+  T (5, "%0*hhd %0*hhi", 2,  7, 2,   9); /* { dg-warning "nul past the end" } */
+  T (5, "%0*hhd %0*hhi", 0, 12, 0, 123); /* { dg-warning ".%0\\*hhi. directive writing 3 bytes into a region of size 2" } */
+  T (5, "%0*hhd %0*hhi", 1, 12, 1, 123); /* { dg-warning ".%0\\*hhi. directive writing 3 bytes into a region of size 2" } */
+  T (5, "%0*hhd %0*hhi", 2, 12, 3, 123); /* { dg-warning ".%0\\*hhi. directive writing 3 bytes into a region of size 2" } */
+
+  /* FIXME: Move the boundary test cases into a file of their own that's
+     exercised only on targets with the matching type limits (otherwise
+     they'll fail).  */
+#undef MAX
+#define MAX   127
+
+#undef MIN
+#define MIN   (-MAX -1)
+
+  T (1, "%hhi",        MAX);    /* { dg-warning "into a region" } */
+  T (1, "%hhi",        MIN);    /* { dg-warning "into a region" } */
+  T (1, "%hhi",  MAX +   1);    /* { dg-warning "into a region" } */
+
+  T (2, "%hhi",  MAX +   1);    /* { dg-warning "into a region" } */
+  T (2, "%hhi",  MAX +  10);    /* { dg-warning "into a region" } */
+  T (2, "%hhi",  MAX + 100);    /* { dg-warning "into a region" } */
+}
+
+/* Exercise the "%hhd", "%hi", "%ho", "%hu", and "%hx" directives
+   with constant arguments.  */
+
+void test_sprintf_chk_h_const (void)
+{
+  T (1, "%hu",          0);     /* { dg-warning "nul past the end" } */
+  T (1, "%hu",          1);     /* { dg-warning "nul past the end" } */
+  T (1, "%hu",         -1);     /* { dg-warning "into a region" } */
+
+  T (2, "%hi",          0);
+  T (2, "%hi",          1);
+  T (2, "%hi",          9);
+  T (2, "% hi",         9);     /* { dg-warning "nul past the end" } */
+  T (2, "%+hi",         9);     /* { dg-warning "nul past the end" } */
+  T (2, "%-hi",         9);
+  T (2, "%hi",         10);     /* { dg-warning "nul past the end" } */
+  T (2, "%hi",         -1);     /* { dg-warning "nul past the end" } */
+  T (2, "% hi",        -2);     /* { dg-warning "nul past the end" } */
+  T (2, "%+hi",        -3);     /* { dg-warning "nul past the end" } */
+  T (2, "%-hi",        -4);     /* { dg-warning "nul past the end" } */
+
+  T (2, "%hu",          0);
+  T (2, "%hu",          1);
+  T (2, "%hu",          9);
+  T (2, "%hu",         10);     /* { dg-warning "nul past the end" } */
+  T (2, "%hu",         -1);     /* { dg-warning "into a region" } */
+
+  T (2, "%ho",          0);
+  T (2, "%ho",          1);
+  T (2, "%ho",          7);
+  T (2, "%ho",        010);     /* { dg-warning "nul past the end" } */
+  T (2, "%ho",        077);     /* { dg-warning "nul past the end" } */
+  T (2, "%ho",       0100);     /* { dg-warning "into a region" } */
+  T (2, "%ho",         -1);     /* { dg-warning "into a region" } */
+
+  T (2, "%hx",          0);
+  T (2, "%hx",          1);
+  T (2, "%hx",          7);
+  T (2, "%hx",        0xf);
+  T (2, "%hx",       0x10);     /* { dg-warning "nul past the end" } */
+  T (2, "%hx",       0xff);     /* { dg-warning "nul past the end" } */
+  T (2, "%hx",      0x100);     /* { dg-warning "into a region" } */
+  T (2, "%hx",         -1);     /* { dg-warning "into a region" } */
+
+  T (3, "% hi",         7);
+  T (3, "%+hi",         8);
+  T (3, "%-hi",         9);
+  T (3, "%hi",         10);
+  T (3, "%hi",         -1);
+  T (3, "% hi",        -2);
+  T (3, "%+hi",        -3);
+  T (3, "%-hi",        -4);
+
+  T (5, "%hu",       9999);
+  T (5, "%hu",      10000);     /* { dg-warning "nul past the end" } */
+  T (5, "%hu",      65535);     /* { dg-warning "nul past the end" } */
+
+  T (1, "%#hx",         0);     /* { dg-warning "nul past the end" } */
+  T (2, "%#hx",         0);
+  T (3, "%#hx",         1);     /* { dg-warning "nul past the end" } */
+
+  T (4, "%#hx",         0);
+  T (4, "%#hx",         1);
+  T (4, "%#hx",       0xf);
+  T (4, "%#hx",      0x10);     /* { dg-warning "nul past the end" } */
+  T (4, "%#hx",      0xff);     /* { dg-warning "nul past the end" } */
+  T (4, "%#hx",     0x100);     /* { dg-warning "into a region" } */
+  T (4, "%#hx",        -1);     /* { dg-warning "into a region" } */
+
+#undef MAX
+#define MAX   65535
+
+  T (1, "%hhu",         0);     /* { dg-warning "nul past the end" } */
+  T (1, "%hhu",         1);     /* { dg-warning "nul past the end" } */
+  T (1, "%hhu",        -1);     /* { dg-warning "into a region" } */
+  T (1, "%hhu",       MAX);     /* { dg-warning "into a region" } */
+  T (1, "%hhu",  MAX +  1);     /* { dg-warning "nul past the end" } */
+}
+
+/* Exercise the "%d", "%i", "%o", "%u", and "%x" directives with
+   constant arguments.  */
+
+void test_sprintf_chk_integer_const (void)
+{
+  T ( 1, "%i",          0);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%i",          1);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%i",         -1);         /* { dg-warning "into a region" } */
+  T ( 1, "%i_",         1);         /* { dg-warning "character ._. at offset 2 past the end" } */
+  T ( 1, "_%i",         1);         /* { dg-warning "into a region" } */
+  T ( 1, "_%i_",        1);         /* { dg-warning "into a region" } */
+  T ( 1, "%o",          0);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%u",          0);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%x",          0);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%#x",         0);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%x",          1);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%#x",         1);         /* { dg-warning "into a region" } */
+
+  T ( 2, "%i",          0);
+  T ( 2, "%i",          1);
+  T ( 2, "%i",          9);
+  T ( 2, "%i",         -1);         /* { dg-warning "nul past the end" } */
+  T ( 2, "%i",         10);         /* { dg-warning "nul past the end" } */
+  T ( 2, "%i_",         0);         /* { dg-warning "nul past the end" } */
+  T ( 2, "_%i",         0);         /* { dg-warning "nul past the end" } */
+  T ( 2, "_%i_",        0);         /* { dg-warning "character ._. at offset 3 past the end" } */
+  T ( 2, "%o",          1);
+  T ( 2, "%o",          7);
+  T ( 2, "%o",        010);         /* { dg-warning "nul past the end" } */
+  T ( 2, "%o",       0100);         /* { dg-warning "into a region" } */
+  T ( 2, "%x",          1);
+  T ( 2, "%#x",         1);         /* { dg-warning "into a region" } */
+  T ( 2, "%x",        0xa);
+  T ( 2, "%x",        0xf);
+  T ( 2, "%x",       0x10);         /* { dg-warning "nul past the end" } */
+  T ( 2, "%x",       0xff);         /* { dg-warning "nul past the end" } */
+  T ( 2, "%x",      0x1ff);         /* { dg-warning "into a region" } */
+
+  T ( 3, "%i",          0);
+  T ( 3, "%i",          1);
+  T ( 3, "%i",          9);
+  T ( 3, "%i",         -9);
+  T ( 3, "%i",         10);
+  T ( 3, "%i",         99);
+  T ( 3, "%i",        -99);         /* { dg-warning "nul past the end" } */
+
+  /* ~0U is formatted into exactly three bytes as "-1" followed by
+     the terminating NUL character.  */
+  T ( 3, "%+i",       ~0U);
+  T ( 3, "%-i",       ~0U);
+  T ( 3, "% i",       ~0U);
+
+  T ( 8, "%8u",         1);        /* { dg-warning "nul past the end" } */
+  T ( 9, "%8u",         1);
+
+  T ( 7, "%1$i%2$i%3$i",     1, 23, 456);
+  T ( 8, "%1$i%2$i%3$i%1$i", 1, 23, 456);
+  T ( 8, "%1$i%2$i%3$i%2$i", 1, 23, 456);   /* { dg-warning "nul past the end" } */
+  T ( 8, "%1$i%2$i%3$i%3$i", 1, 23, 456);   /* { dg-warning "into a region" } */
+
+#undef MAX
+#define MAX   2147483647   /* 10 digits. */
+#undef MIN
+#define MIN   (-MAX -1)    /* Sign plus 10 digits. */
+
+  T ( 1, "%i",        MAX);         /* { dg-warning "into a region" } */
+  T ( 1, "%i",        MIN);         /* { dg-warning "into a region" } */
+  T ( 2, "%i",        MAX);         /* { dg-warning "into a region" } */
+  T ( 2, "%i",        MIN);         /* { dg-warning "into a region" } */
+  T (10, "%i",  123456789);
+  T (10, "%i", -123456789);         /* { dg-warning "nul past the end" } */
+  T (10, "%i",        MAX);         /* { dg-warning "nul past the end" } */
+  T (10, "%i",        MIN);         /* { dg-warning "into a region" } */
+
+  T (11, "%i",        MAX);
+  T (11, "%i",        MIN);         /* { dg-warning "nul past the end" } */
+}
+
+/* Exercise the "%jd", "%ji", "%jo", "%ju", and "%jx" directives
+   for the formatting of intmax_t and uintmax_t values with constant
+   arguments.  */
+
+void test_sprintf_chk_j_const (void)
+{
+#define I(x) ((__INTMAX_TYPE__)x)
+
+  T ( 1, "%ji",  I (    0));      /* { dg-warning "nul past the end" } */
+  T ( 1, "%ji",  I (    1));      /* { dg-warning "nul past the end" } */
+  T ( 1, "%ji",  I (   -1));      /* { dg-warning "into a region" } */
+  T ( 1, "%ji_", I (    1));      /* { dg-warning "character ._. at offset 3 past the end" } */
+  T ( 1, "_%ji", I (    1));      /* { dg-warning "into a region" } */
+  T ( 1, "_%ji_",I (    1));      /* { dg-warning "into a region" } */
+  T ( 1, "%jo",  I (    0));      /* { dg-warning "nul past the end" } */
+  T ( 1, "%ju",  I (    0));      /* { dg-warning "nul past the end" } */
+  T ( 1, "%jx",  I (    0));      /* { dg-warning "nul past the end" } */
+  T ( 1, "%#jx", I (    0));      /* { dg-warning "nul past the end" } */
+  T ( 1, "%jx",  I (    1));      /* { dg-warning "nul past the end" } */
+  T ( 1, "%#jx", I (    1));      /* { dg-warning "into a region" } */
+
+  T ( 2, "%ji",  I (    0));
+  T ( 2, "%ji",  I (    1));
+  T ( 2, "%ji",  I (    9));
+  T ( 2, "%ji",  I (   -1));      /* { dg-warning "nul past the end" } */
+  T ( 2, "%ji",  I (   10));      /* { dg-warning "nul past the end" } */
+  T ( 2, "%ji_", I (    0));      /* { dg-warning "nul past the end" } */
+  T ( 2, "_%ji", I (    0));      /* { dg-warning "nul past the end" } */
+  T ( 2, "_%ji_",I (    0));      /* { dg-warning "character ._. at offset 4 past the end" } */
+  T ( 2, "%jo",  I (    1));
+  T ( 2, "%jo",  I (    7));
+  T ( 2, "%jo",  I (  010));      /* { dg-warning "nul past the end" } */
+  T ( 2, "%jo",  I ( 0100));      /* { dg-warning "into a region" } */
+  T ( 2, "%jx",  I (    1));
+  T ( 2, "%#jx", I (    1));      /* { dg-warning "into a region" } */
+  T ( 2, "%jx",  I (  0xa));
+  T ( 2, "%jx",  I (  0xf));
+  T ( 2, "%jx",  I ( 0x10));      /* { dg-warning "nul past the end" } */
+  T ( 2, "%jx",  I ( 0xff));      /* { dg-warning "nul past the end" } */
+  T ( 2, "%jx",  I (0x1ff));      /* { dg-warning "into a region" } */
+
+  T ( 3, "%ji",  I (    0));
+  T ( 3, "%ji",  I (    1));
+  T ( 3, "%ji",  I (    9));
+  T ( 3, "%ji",  I (   -9));
+  T ( 3, "%ji",  I (   10));
+  T ( 3, "%ji",  I (   99));
+  T ( 3, "%ji",  I (  -99));      /* { dg-warning "nul past the end" } */
+
+  /* ~0 is formatted into exactly three bytes as "-1" followed by
+     the terminating NUL character.  */
+  T ( 3, "%+ji",    ~I (0));
+  T ( 3, "%-ji",    ~I (0));
+  T ( 3, "% ji",    ~I (0));
+
+  T ( 8, "%8ju",     I (1));      /* { dg-warning "nul past the end" } */
+  T ( 9, "%8ju",     I (1));
+}
+
+/* Exercise the "%ld", "%li", "%lo", "%lu", and "%lx" directives
+   with constant arguments.  */
+
+void test_sprintf_chk_l_const (void)
+{
+  T ( 1, "%li",      0L);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%li",      1L);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%li",     -1L);         /* { dg-warning "into a region" } */
+  T ( 1, "%li_",     1L);         /* { dg-warning "character ._. at offset 3 past the end" } */
+  T ( 1, "_%li",     1L);         /* { dg-warning "into a region" } */
+  T ( 1, "_%li_",    1L);         /* { dg-warning "into a region" } */
+  T ( 1, "%lo",      0L);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%lu",      0L);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%lx",      0L);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%#lx",     0L);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%lx",      1L);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%#lx",     1L);         /* { dg-warning "into a region" } */
+
+  T ( 2, "%li",      0L);
+  T ( 2, "%li",      1L);
+  T ( 2, "%li",      9L);
+  T ( 2, "%li",     -1L);         /* { dg-warning "nul past the end" } */
+  T ( 2, "%li",     10L);         /* { dg-warning "nul past the end" } */
+  T ( 2, "%li_",     0L);         /* { dg-warning "nul past the end" } */
+  T ( 2, "_%li",     0L);         /* { dg-warning "nul past the end" } */
+  T ( 2, "_%li_",    0L);         /* { dg-warning "character ._. at offset 4 past the end" } */
+  T ( 2, "%lo",      1L);
+  T ( 2, "%lo",      7L);
+  T ( 2, "%lo",    010L);         /* { dg-warning "nul past the end" } */
+  T ( 2, "%lo",   0100L);         /* { dg-warning "into a region" } */
+  T ( 2, "%lx",      1L);
+  T ( 2, "%#lx",     1L);         /* { dg-warning "into a region" } */
+  T ( 2, "%lx",    0xaL);
+  T ( 2, "%lx",    0xfL);
+  T ( 2, "%lx",   0x10L);         /* { dg-warning "nul past the end" } */
+  T ( 2, "%lx",   0xffL);         /* { dg-warning "nul past the end" } */
+  T ( 2, "%lx",  0x1ffL);         /* { dg-warning "into a region" } */
+
+  T ( 3, "%li",      0L);
+  T ( 3, "%li",      1L);
+  T ( 3, "%li",      9L);
+  T ( 3, "%li",     -9L);
+  T ( 3, "%li",     10L);
+  T ( 3, "%li",     99L);
+  T ( 3, "%li",    -99L);         /* { dg-warning "nul past the end" } */
+
+  /* ~0U is formatted into exactly three bytes as "-1" followed by
+     the terminating NUL character.  */
+  T ( 3, "%+li",   ~0LU);
+  T ( 3, "%-li",   ~0LU);
+  T ( 3, "% li",   ~0LU);
+
+  T ( 8, "%8lu",     1L);         /* { dg-warning "nul past the end" } */
+  T ( 9, "%8lu",     1L);
+}
+
+/* Exercise the "%lld", "%lli", "%llo", "%llu", and "%llx" directives
+   with constant arguments.  */
+
+void test_sprintf_chk_ll_const (void)
+{
+  T ( 1, "%lli",      0LL);     /* { dg-warning "nul past the end" } */
+  T ( 1, "%lli",      1LL);     /* { dg-warning "nul past the end" } */
+  T ( 1, "%lli",     -1LL);     /* { dg-warning "into a region" } */
+  T ( 1, "%lli_",     1LL);     /* { dg-warning "character ._. at offset 4 past the end" } */
+  T ( 1, "_%lli",     1LL);     /* { dg-warning "into a region" } */
+  T ( 1, "_%lli_",    1LL);     /* { dg-warning "into a region" } */
+  T ( 1, "%llo",      0LL);     /* { dg-warning "nul past the end" } */
+  T ( 1, "%llu",      0LL);     /* { dg-warning "nul past the end" } */
+  T ( 1, "%llx",      0LL);     /* { dg-warning "nul past the end" } */
+  T ( 1, "%#llx",     0LL);     /* { dg-warning "nul past the end" } */
+  T ( 1, "%llx",      1LL);     /* { dg-warning "nul past the end" } */
+  T ( 1, "%#llx",     1LL);     /* { dg-warning "into a region" } */
+
+  T ( 2, "%lli",      0LL);
+  T ( 2, "%lli",      1LL);
+  T ( 2, "%lli",      9LL);
+  T ( 2, "%lli",     -1LL);     /* { dg-warning "nul past the end" } */
+  T ( 2, "%lli",     10LL);     /* { dg-warning "nul past the end" } */
+  T ( 2, "%lli_",     0LL);     /* { dg-warning "nul past the end" } */
+  T ( 2, "_%lli",     0LL);     /* { dg-warning "nul past the end" } */
+  T ( 2, "_%lli_",    0LL);     /* { dg-warning "character ._. at offset 5 past the end" } */
+  T ( 2, "%llo",      1LL);
+  T ( 2, "%llo",      7LL);
+  T ( 2, "%llo",    010LL);     /* { dg-warning "nul past the end" } */
+  T ( 2, "%llo",   0100LL);     /* { dg-warning "into a region" } */
+  T ( 2, "%llx",      1LL);
+  T ( 2, "%#llx",     1LL);     /* { dg-warning "into a region" } */
+  T ( 2, "%llx",    0xaLL);
+  T ( 2, "%llx",    0xfLL);
+  T ( 2, "%llx",   0x10LL);     /* { dg-warning "nul past the end" } */
+  T ( 2, "%llx",   0xffLL);     /* { dg-warning "nul past the end" } */
+  T ( 2, "%llx",  0x1ffLL);     /* { dg-warning "into a region" } */
+
+  T ( 3, "%lli",      0LL);
+  T ( 3, "%lli",      1LL);
+  T ( 3, "%lli",      9LL);
+  T ( 3, "%lli",     -9LL);
+  T ( 3, "%lli",     10LL);
+  T ( 3, "%lli",     99LL);
+  T ( 3, "%lli",    -99LL);     /* { dg-warning "nul past the end" } */
+
+  /* ~0U is formatted into exactly three bytes as "-1" followed by
+     the terminating NUL character.  */
+  T ( 3, "%+lli",   ~0LLU);
+  T ( 3, "%-lli",   ~0LLU);
+  T ( 3, "% lli",   ~0LLU);
+
+  T ( 8, "%8llu",     1LL);     /* { dg-warning "nul past the end" } */
+  T ( 9, "%8llu",     1LL);
+
+  /* assume 64-bit long long.  */
+#define LLONG_MAX   9223372036854775807LL   /* 19 bytes */
+#define LLONG_MIN   (-LLONG_MAX - 1)        /* 20 bytes */
+
+  T (18, "%lli", LLONG_MIN);    /* { dg-warning "into a region" } */
+  T (19, "%lli", LLONG_MIN);    /* { dg-warning "into a region" } */
+  T (20, "%lli", LLONG_MIN);    /* { dg-warning "nul past the end" } */
+  T (21, "%lli", LLONG_MIN);
+
+  T (18, "%lli", LLONG_MAX);    /* { dg-warning "into a region" } */
+  T (19, "%lli", LLONG_MAX);    /* { dg-warning "nul past the end" } */
+  T (20, "%lli", LLONG_MAX);
+
+  T (21, "%llo",      -1LL);    /* { dg-warning "into a region" } */
+  T (22, "%llo",      -1LL);    /* { dg-warning "nul past the end" } */
+  T (23, "%llo",      -1LL);
+
+  T (19, "%llu",      -1LL);    /* { dg-warning "into a region" } */
+  T (20, "%llu",      -1LL);    /* { dg-warning "nul past the end" } */
+  T (21, "%llu",      -1LL);
+
+  T (15, "%llx",      -1LL);    /* { dg-warning "into a region" } */
+  T (16, "%llx",      -1LL);    /* { dg-warning "nul past the end" } */
+  T (17, "%llx",      -1LL);
+}
+
+void test_sprintf_chk_L_const (void)
+{
+  T (-1, "%Li",        0LL);
+  T ( 1, "%Li",        0LL);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%Li",        1LL);         /* { dg-warning "nul past the end" } */
+  T ( 1, "%Li",       -1LL);         /* { dg-warning "into a region" } */
+  T ( 1, "%Li_",       1LL);         /* { dg-warning "character ._. at offset 3 past the end" } */
+  T ( 1, "_%Li",       1LL);         /* { dg-warning "into a region" } */
+  T ( 1, "_%Li_",      1LL);         /* { dg-warning "into a region" } */
+}
+
+void test_sprintf_chk_z_const (void)
+{
+  T (-1, "%zi",        (size_t)0);
+  T ( 1, "%zi",        (size_t)0);  /* { dg-warning "nul past the end" } */
+  T ( 1, "%zi",        (size_t)1);  /* { dg-warning "nul past the end" } */
+  T ( 1, "%zi",        (size_t)-1L);/* { dg-warning "into a region" } */
+  T ( 1, "%zi_",       (size_t)1);  /* { dg-warning "character ._. at offset 3 past the end" } */
+  T ( 1, "_%zi",       (size_t)1);  /* { dg-warning "into a region" } */
+  T ( 1, "_%zi_",      (size_t)1);  /* { dg-warning "into a region" } */
+
+  T ( 2, "%zu",        (size_t)1);
+  T ( 2, "%zu",        (size_t)9);
+  T ( 2, "%zu",        (size_t)10); /* { dg-warning "nul past the end" } */
+}
+
+void test_sprintf_chk_e_const (void)
+{
+  T (-1, "%E",   0.0);
+  T ( 0, "%E",   0.0);          /* { dg-warning "into a region" } */
+  T ( 0, "%e",   0.0);          /* { dg-warning "into a region" } */
+  T ( 1, "%E",   1.0);          /* { dg-warning "into a region" } */
+  T ( 1, "%e",   1.0);          /* { dg-warning "into a region" } */
+  T ( 2, "%e",   2.0);          /* { dg-warning "into a region" } */
+  T ( 3, "%e",   3.0);          /* { dg-warning "into a region" } */
+  T (12, "%e",   1.2);          /* { dg-warning "nul past the end" } */
+  T (12, "%e",  12.0);          /* { dg-warning "nul past the end" } */
+  T (13, "%e",   1.3);          /* 1.300000e+00 */
+  T (13, "%E",  13.0);          /* 1.300000e+01 */
+  T (13, "%e",  13.0);
+  T (13, "%E",  1.4e+99);       /* 1.400000e+99 */
+  T (13, "%e",  1.5e+100);      /* { dg-warning "nul past the end" } */
+  T (14, "%E",  1.6e+101);      /* 1.600000E+101 */
+  T (14, "%e", -1.7e+102);      /* { dg-warning "nul past the end" } */
+  T (15, "%E", -1.8e+103);      /* -1.800000E+103 */
+
+  T (16, "%.8e", -1.9e+104);    /* { dg-warning "nul past the end" } */
+  T (17, "%.8e", -2.0e+105);    /* -2.00000000e+105 */
+
+  T ( 5, "%.0e", 0.0);          /* { dg-warning "nul past the end" } */
+  T ( 5, "%.0e", 1.0);          /* { dg-warning "nul past the end" } */
+  T ( 6, "%.0e", 1.0);
+
+  /* The actual output of the following directives depends on the rounding
+     mode.  Verify that the warning correctly reflects that.  */
+  T (12, "%e",  9.999999e+99);  /* { dg-warning "directive writing between 12 and 13 bytes" } */
+  T (12, "%e",  9.9999994e+99); /* { dg-warning "directive writing between 12 and 13 bytes" } */
+  T (12, "%e",  9.9999995e+99); /* { dg-warning "directive writing between 12 and 13 bytes" } */
+  T (12, "%e",  9.9999996e+99); /* { dg-warning "directive writing between 12 and 13 bytes" } */
+  T (12, "%e",  9.9999997e+99); /* { dg-warning "directive writing between 12 and 13 bytes" } */
+  T (12, "%e",  9.9999998e+99); /* { dg-warning "directive writing between 12 and 13 bytes" } */
+
+  T (12, "%Le", 9.9999994e+99L);/* { dg-warning "directive writing between 12 and 13 bytes" } */
+  T (12, "%Le", 9.9999995e+99L);/* { dg-warning "directive writing between 12 and 13 bytes" } */
+  T (12, "%Le", 9.9999996e+99L);/* { dg-warning "directive writing between 12 and 13 bytes" } */
+  T (12, "%Le", 9.9999997e+99L);/* { dg-warning "directive writing between 12 and 13 bytes" } */
+  T (12, "%Le", 9.9999998e+99L);/* { dg-warning "directive writing between 12 and 13 bytes" } */
+  T (12, "%Le", 9.9999999e+99L);/* { dg-warning "directive writing between 12 and 13 bytes" } */
+}
+
+/* At -Wformat-length level 1 unknown numbers are assumed to have
+   the value one, and unknown strings are assumed to have a zero
+   length.  */
+
+void test_sprintf_chk_s_nonconst (int i, const char *s)
+{
+  T (-1, "%s",   s);
+  T ( 0, "%s",   s);            /* { dg-warning "nul past the end" } */
+  T ( 1, "%s",   s);
+  T ( 1, "%.0s", s);
+  T ( 1, "%.1s", s);            /* { dg-warning "nul past the end" } */
+
+  /* The following will definitely write past the end of the buffer,
+     but since at level 1 the length of an unknown string argument
+     is assumed to be zero, it will write the terminating nul past
+     the end (we don't print "past the end" when we're not
+     sure which we can't be with an unknown string.  */
+  T (1, "%1s",  s);             /* { dg-warning "writing a terminating nul past the end" } */
+}
+
+/* Exercise the hh length modifier with all integer specifiers and
+   a non-constant argument.  */
+
+void test_sprintf_chk_hh_nonconst (int a)
+{
+  T (-1, "%hhd",        a);
+
+  T (0, "%hhd",         a);     /* { dg-warning "into a region" } */
+  T (0, "%hhi",         a);     /* { dg-warning "into a region" } */
+  T (0, "%hhu",         a);     /* { dg-warning "into a region" } */
+  T (0, "%hhx",         a);     /* { dg-warning "into a region" } */
+
+  T (1, "%hhd",         a);     /* { dg-warning "nul past the end" } */
+  T (1, "%hhi",         a);     /* { dg-warning "nul past the end" } */
+  T (1, "%hhu",         a);     /* { dg-warning "nul past the end" } */
+  T (1, "%hhx",         a);     /* { dg-warning "nul past the end" } */
+
+  T (1, "% hhd",        a);     /* { dg-warning "into a region" } */
+  T (1, "% hhi",        a);     /* { dg-warning "into a region" } */
+  T (1, "%+hhd",        a);     /* { dg-warning "into a region" } */
+  T (1, "%+hhi",        a);     /* { dg-warning "into a region" } */
+  T (1, "%-hhd",        a);     /* { dg-warning "nul past the end" } */
+  T (1, "%-hhi",        a);     /* { dg-warning "nul past the end" } */
+
+  T (2, "%hhd",         a);
+  T (2, "%hhi",         a);
+  T (2, "%hho",         a);
+  T (2, "%hhu",         a);
+  T (2, "%hhx",         a);
+
+  T (2, "% hhd",        a);     /* { dg-warning "nul past the end" } */
+  T (2, "% hhi",        a);     /* { dg-warning "nul past the end" } */
+  T (2, "% hho",        a);     /* { dg-warning ". . flag used with .%o." } */
+  T (2, "% hhu",        a);     /* { dg-warning ". . flag used with .%u." } */
+  T (2, "% hhx",        a);     /* { dg-warning ". . flag used with .%x." } */
+
+  T (2, "#%hho",        a);     /* { dg-warning "nul past the end" } */
+  T (2, "#%hhx",        a);     /* { dg-warning "nul past the end" } */
+
+  T (3, "%2hhd",        a);
+  T (3, "%2hhi",        a);
+  T (3, "%2hho",        a);
+  T (3, "%2hhu",        a);
+  T (3, "%2hhx",        a);
+
+  /* Exercise cases where the type of the actual argument (whose value
+     and range are unknown) constrain the size of the output and so
+     can be used to avoid what would otherwise be false positives.  */
+
+  T (2, "%hhd", (UChar)a);
+  T (2, "%hhi", (UChar)a);
+  T (2, "%-hhi", (UChar)a);
+}
+
+/* Exercise the h length modifier with all integer specifiers and
+   a non-constant argument.  */
+
+void test_sprintf_chk_h_nonconst (int a)
+{
+  T (-1, "%hd",         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 (-1, "%d",          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 (-1, "%E",          d);
+  T ( 0, "%E",          d);           /* { dg-warning "writing between 12 and 14 bytes into a region of size 0" } */
+  T ( 0, "%e",          d);           /* { dg-warning "into a region" } */
+  T ( 1, "%E",          d);           /* { dg-warning "into a region" } */
+  T ( 1, "%e",          d);           /* { dg-warning "into a region" } */
+  T ( 2, "%e",          d);           /* { dg-warning "into a region" } */
+  T ( 3, "%e",          d);           /* { dg-warning "into a region" } */
+  T (12, "%e",          d);           /* { dg-warning "past the end" } */
+  T (12, "%e",          d);           /* { dg-warning "past the end" } */
+  T (13, "%E",          d);           /* 1.000000E+00 */
+  T (13, "%e",          d);
+  T (14, "%E",          d);
+  T (14, "%e",          d);
+
+  T  (0, "%+E",         d);           /* { dg-warning "writing between 13 and 14 bytes into a region of size 0" } */
+  T  (0, "%-e",         d);           /* { dg-warning "writing between 12 and 14 bytes into a region of size 0" } */
+  T  (0, "% E",         d);           /* { dg-warning "writing between 13 and 14 bytes into a region of size 0" } */
+
+  /* The range of output of "%.0e" is between 5 and 7 bytes (not counting
+     the terminating NUL.  */
+  T ( 5, "%.0e",        d);           /* { dg-warning "writing a terminating nul past the end" } */
+  T ( 6, "%.0e",        d);           /* 1e+00 */
+
+  /* The range of output of "%.1e" is between 7 and 9 bytes (not counting
+     the terminating NUL.  */
+  T ( 7, "%.1e",        d);           /* { dg-warning "writing a terminating nul past the end" } */
+  T ( 8, "%.1e",        d);
+}
+
+void test_sprintf_chk_f_nonconst (double d)
+{
+  T (-1, "%F",          d);
+  T ( 0, "%F",          d);           /* { dg-warning "into a region" } */
+  T ( 0, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 1, "%F",          d);           /* { dg-warning "into a region" } */
+  T ( 1, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 2, "%F",          d);           /* { dg-warning "into a region" } */
+  T ( 2, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 3, "%F",          d);           /* { dg-warning "into a region" } */
+  T ( 3, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 4, "%F",          d);           /* { dg-warning "into a region" } */
+  T ( 4, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 5, "%F",          d);           /* { dg-warning "into a region" } */
+  T ( 5, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 6, "%F",          d);           /* { dg-warning "into a region" } */
+  T ( 6, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 7, "%F",          d);           /* { dg-warning "into a region" } */
+  T ( 7, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 8, "%F",          d);           /* { dg-warning "nul past the end" } */
+  T ( 8, "%f",          d);           /* { dg-warning "nul past the end" } */
+  T ( 9, "%F",          d);
+  T ( 9, "%f",          d);
+}
+
+/* Tests for __builtin_vsprintf_chk are the same as those for
+   __builtin_sprintf_chk with non-constant arguments.  */
+#undef T
+#define T(size, fmt)							\
+  __builtin___vsprintf_chk (buffer (size), 0, objsize (size), fmt, va)
+
+void test_vsprintf_chk_c (__builtin_va_list va)
+{
+  T (-1, "%c");
+
+  /* Verify the full text of the diagnostic for just the distinct messages
+     and use abbreviations in subsequent test cases.  */
+  T (0, "%c");              /* { dg-warning ".%c. directive writing 1 byte into a region of size 0" } */
+  T (1, "%c");              /* { dg-warning "writing a terminating nul past the end" } */
+  T (1, "%c");              /* { dg-warning "nul past the end" } */
+  T (2, "%c");
+  T (2, "%2c");             /* { dg-warning "nul past the end" } */
+  T (2, "%3c");             /* { dg-warning "into a region" } */
+  T (2, "%c%c");            /* { dg-warning "nul past the end" } */
+  T (3, "%c%c");
+
+  /* Wide characters.  */
+  T (0, "%lc");             /* { dg-warning "nul past the end" } */
+  T (1, "%lc");
+  T (2, "%lc");
+
+  /* The following could result in as few as a single byte and in as many
+     as MB_CUR_MAX, but since the MB_CUR_MAX value is a runtime property
+     the write cannot be reliably diagnosed.  */
+  T (2, "%lc");
+  T (2, "%1lc");
+  /* Writing some unknown number of bytes into a field two characters wide.  */
+  T (2, "%2lc");            /* { dg-warning "nul past the end" } */
+  T (2, "%lc%lc");
+
+  T (3, "%lc%c");
+  /* Here in the best case each argument will format as single character,
+     causing the terminating NUL to be written past the end.  */
+  T (3, "%lc%c%c");
+
+}
+
+void test_vsprintf_chk_int (__builtin_va_list va)
+{
+  T (-1, "%d");
+
+  T (0, "%d");                /* { dg-warning "into a region" } */
+  T (0, "%i");                /* { dg-warning "into a region" } */
+  T (0, "%u");                /* { dg-warning "into a region" } */
+  T (0, "%x");                /* { dg-warning "into a region" } */
+
+  T (1, "%d");                /* { dg-warning "nul past the end" } */
+  T (1, "%i");                /* { dg-warning "nul past the end" } */
+  T (1, "%u");                /* { dg-warning "nul past the end" } */
+  T (1, "%x");                /* { dg-warning "nul past the end" } */
+
+  T (1, "% d");               /* { dg-warning "into a region" } */
+  T (1, "% i");               /* { dg-warning "into a region" } */
+  T (1, "%+d");               /* { dg-warning "into a region" } */
+  T (1, "%+i");               /* { dg-warning "into a region" } */
+  T (1, "%-d");               /* { dg-warning "nul past the end" } */
+  T (1, "%-i");               /* { dg-warning "nul past the end" } */
+
+  T (2, "%d");
+  T (2, "%i");
+  T (2, "%o");
+  T (2, "%u");
+  T (2, "%x");
+
+  T (2, "% d");               /* { dg-warning "nul past the end" } */
+  T (2, "% i");               /* { dg-warning "nul past the end" } */
+  T (2, "% o");               /* { dg-warning ". . flag used with .%o." } */
+  T (2, "% u");               /* { dg-warning ". . flag used with .%u." } */
+  T (2, "% x");               /* { dg-warning ". . flag used with .%x." } */
+
+  T (2, "#%o");               /* { dg-warning "nul past the end" } */
+  T (2, "#%x");               /* { dg-warning "nul past the end" } */
+
+  T (3, "%2d");
+  T (3, "%2i");
+  T (3, "%2o");
+  T (3, "%2u");
+  T (3, "%2x");
+}
+
+#undef T
+#define T(size, fmt, ...)						\
+  __builtin_snprintf (buffer (size), objsize (size), fmt, __VA_ARGS__)
+
+void test_snprintf_c_const (void)
+{
+  T (-1, "%c",    0);            /* { dg-warning "specified destination size \[^ \]* too large" } */
+
+  /* Verify the full text of the diagnostic for just the distinct messages
+     and use abbreviations in subsequent test cases.  */
+
+  /* A call to snprintf with a buffer of zero size is a request to determine
+     the size of output without writing anything into the destination. No
+     warning must be issued.  */
+  T (0, "%c",     0);
+  T (1, "%c",     0);            /* { dg-warning "output truncated before the last format character" } */
+  T (1, "%c",   '1');            /* { dg-warning "output truncated" } */
+  T (2, "%c",   '1');
+  T (2, "%2c",  '1');            /* { dg-warning "output truncated" } */
+  T (2, "%3c",  '1');            /* { dg-warning "directive output truncated" } */
+  T (2, "%c%c", '1', '2');       /* { dg-warning "output truncated" } */
+  T (3, "%c%c", '1', '2');
+
+  /* Wide characters.  */
+  T (0, "%lc",  0);
+  T (1, "%lc",  0);
+  T (2, "%lc",  0);
+
+  /* The following could result in as few as a single byte and in as many
+     as MB_CUR_MAX, but since the MB_CUR_MAX value is a runtime property
+     the write cannot be reliably diagnosed.  */
+  T (2, "%lc",  L'1');
+  T (2, "%1lc", L'1');
+  /* Writing at least 1 characted into a field two characters wide.  */
+  T (2, "%2lc", L'1');          /* { dg-warning "output truncated before the last format character" } */
+
+  T (3, "%lc%c",   L'1', '2');
+  /* Here in the best case each argument will format as single character,
+     causing the output to be truncated just before the terminating NUL
+     (i.e., cutting off the '3').  */
+  T (3, "%lc%c%c", L'1', '2', '3');   /* { dg-warning "output truncated" } */
+  T (3, "%lc%lc%c", L'1', L'2', '3'); /* { dg-warning "output truncated" } */
+}
+
+#undef T
+#define T(size, fmt, ...)						\
+  __builtin___snprintf_chk (buffer (size), objsize (size),		\
+			    0, objsize (size), fmt, __VA_ARGS__)
+
+void test_snprintf_chk_c_const (void)
+{
+  /* Verify that specifying a size of the destination buffer that's
+     bigger than its actual size (normally determined and passed to
+     the function by __builtin_object_size) is diagnosed.  */
+  __builtin___snprintf_chk (buffer, 3, 0, 2, " ");   /* { dg-warning "always overflow|specified size 3 exceeds the size 2 of the destination" } */
+
+  T (-1, "%c",    0);           /* { dg-warning "specified destination size \[^ \]* too large" } */
+
+  T (0, "%c",     0);
+  T (0, "%c%c",   0, 0);
+  T (0, "%c_%c",  0, 0);
+  T (0, "_%c_%c", 0, 0);
+
+  T (1, "%c",     0);            /* { dg-warning "output truncated before the last format character" } */
+  T (1, "%c",   '1');            /* { dg-warning "output truncated" } */
+  T (2, "%c",   '1');
+  T (2, "%2c",  '1');            /* { dg-warning "output truncated" } */
+  T (2, "%3c",  '1');            /* { dg-warning "directive output truncated" } */
+  T (2, "%c%c", '1', '2');       /* { dg-warning "output truncated before the last format character" } */
+  T (3, "%c%c", '1', '2');
+  T (3, "%c_%c", '1', '2');      /* { dg-warning "output truncated" } */
+
+  /* Wide characters.  */
+  T (0, "%lc",  0);
+  T (1, "%lc",  0);
+  T (2, "%lc",  0);
+
+  /* The following could result in as few as a single byte and in as many
+     as MB_CUR_MAX, but since the MB_CUR_MAX value is a runtime property
+     the write cannot be reliably diagnosed.  */
+  T (2, "%lc",  L'1');
+  T (2, "%1lc", L'1');
+  /* Writing at least 1 characted into a field two characters wide.  */
+  T (2, "%2lc", L'1');          /* { dg-warning "output truncated before the last format character" } */
+
+  T (3, "%lc%c",   L'1', '2');
+  /* Here in the best case each argument will format as single character,
+     causing the output to be truncated just before the terminating NUL
+     (i.e., cutting off the '3').  */
+  T (3, "%lc%c%c", L'1', '2', '3');   /* { dg-warning "output truncated" } */
+  T (3, "%lc%lc%c", L'1', L'2', '3'); /* { dg-warning "output truncated" } */
+}
+
+/* Macro to verify that calls to __builtin_vsprintf (i.e., with no size
+   argument) issue diagnostics by correctly determining the size of
+   the destination buffer.  */
+#undef T
+#define T(size, fmt)				\
+  __builtin_vsprintf (buffer (size), fmt, va)
+
+void test_vsprintf_s (__builtin_va_list va)
+{
+  T (-1, "%s");
+
+  T (0, "%s");              /* { dg-warning "writing a terminating nul past the end" } */
+  T (1, "%s");
+  T (1, "%1s");             /* { dg-warning "writing a terminating nul past the end" } */
+
+  T (2, "%s%s");
+  T (2, "%s%s_");
+  T (2, "%s_%s");
+  T (2, "_%s%s");
+  T (2, "_%s_%s");          /* { dg-warning "writing a terminating nul past the end" } */
+}
+
+/* Exercise all integer specifiers with no modifier and a non-constant
+   argument.  */
+
+void test_vsprintf_int (__builtin_va_list va)
+{
+  T (-1, "%d");
+
+  T (0, "%d");     /* { dg-warning "into a region" } */
+  T (0, "%i");     /* { dg-warning "into a region" } */
+  T (0, "%u");     /* { dg-warning "into a region" } */
+  T (0, "%x");     /* { dg-warning "into a region" } */
+
+  T (1, "%d");     /* { dg-warning "nul past the end" } */
+  T (1, "%i");     /* { dg-warning "nul past the end" } */
+  T (1, "%u");     /* { dg-warning "nul past the end" } */
+  T (1, "%x");     /* { dg-warning "nul past the end" } */
+
+  T (1, "% d");     /* { dg-warning "into a region" } */
+  T (1, "% i");     /* { dg-warning "into a region" } */
+  T (1, "%+d");     /* { dg-warning "into a region" } */
+  T (1, "%+i");     /* { dg-warning "into a region" } */
+  T (1, "%-d");     /* { dg-warning "nul past the end" } */
+  T (1, "%-i");     /* { dg-warning "nul past the end" } */
+
+  T (2, "%d");
+  T (2, "%i");
+  T (2, "%o");
+  T (2, "%u");
+  T (2, "%x");
+
+  T (2, "% d");     /* { dg-warning "nul past the end" } */
+  T (2, "% i");     /* { dg-warning "nul past the end" } */
+  T (2, "% o");     /* { dg-warning ". . flag used with .%o." } */
+  T (2, "% u");     /* { dg-warning ". . flag used with .%u." } */
+  T (2, "% x");     /* { dg-warning ". . flag used with .%x." } */
+
+  T (2, "#%o");     /* { dg-warning "nul past the end" } */
+  T (2, "#%x");     /* { dg-warning "nul past the end" } */
+
+  T (3, "%2d");
+  T (3, "%2i");
+  T (3, "%2o");
+  T (3, "%2u");
+  T (3, "%2x");
+}
+
+#undef T
+#define T(size, fmt)							\
+  __builtin_vsnprintf (buffer (size), objsize (size), fmt, va)
+
+void test_vsnprintf_s (__builtin_va_list va)
+{
+  T (-1, "%s");             /* { dg-warning "specified destination size \[^ \]* too large" } */
+
+  T (0, "%s");
+  T (1, "%s");
+  T (1, "%1s");             /* { dg-warning "output truncated before the last format character" } */
+
+  T (2, "%s%s");
+  T (2, "%s%s_");
+  T (2, "%s_%s");
+  T (2, "_%s%s");
+  T (2, "_%s_%s");          /* { dg-warning "output truncated before the last format character" } */
+}
+
+#undef T
+#define T(size, fmt)							\
+  __builtin___vsnprintf_chk (buffer (size), objsize (size),		\
+			     0, objsize (size), fmt, va)
+
+void test_vsnprintf_chk_s (__builtin_va_list va)
+{
+  /* Verify that specifying a size of the destination buffer that's
+     bigger than its actual size (normally determined and passed to
+     the function by __builtin_object_size) is diagnosed.  */
+  __builtin___snprintf_chk (buffer, 123, 0, 122, " ");   /* { dg-warning "always overflow|specified size 123 exceeds the size 122 of the destination object" } */
+
+  __builtin___snprintf_chk (buffer, __SIZE_MAX__, 0, 2, " ");   /* { dg-warning "always overflow|destination size .\[0-9\]+. too large" } */
+
+  T (0, "%s");
+  T (1, "%s");
+  T (1, "%1s");             /* { dg-warning "output truncated before the last format character" } */
+
+  T (2, "%s%s");
+  T (2, "%s%s_");
+  T (2, "%s_%s");
+  T (2, "_%s%s");
+  T (2, "_%s_%s");          /* { dg-warning "output truncated before the last format character" } */
+}
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-2.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-2.c
new file mode 100644
index 0000000..e7dcd46
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-2.c
@@ -0,0 +1,214 @@ 
+/* { dg-do compile } */
+/* { dg-options "-std=c99 -Wformat -Wformat-length=2 -ftrack-macro-expansion=0" } */
+
+/* When debugging, define LINE to the line number of the test case to exercise
+   and avoid exercising any of the others.  The buffer and objsize macros
+   below make use of LINE to avoid warnings for other lines.  */
+#ifndef LINE
+# define LINE 0
+#endif
+
+char buffer [256];
+extern char *ptr;
+
+#define buffer(size)							\
+  (!LINE || __LINE__ == LINE ? buffer + sizeof buffer - size : ptr)
+
+#define objsize(size)  (!LINE || __LINE__ == LINE ? size : __SIZE_MAX__ / 2)
+
+typedef __SIZE_TYPE__ size_t;
+
+#if !__cplusplus
+typedef __WCHAR_TYPE__ wchar_t;
+#endif
+
+typedef unsigned char UChar;
+
+#define T(size, fmt, ...)				\
+  __builtin_sprintf (buffer (size), fmt, __VA_ARGS__)
+
+__builtin_va_list va;
+
+/* Exercise buffer overflow detection with const string arguments.  */
+
+void test_s_const (void)
+{
+    /* Wide string literals are handled slightly differently than
+       at level 1.  At level 1, each wide character is assumed to
+       convert into a single byte.  At level 2, they are assumed
+       to convert into at least one byte.  */
+  T (0, "%ls",      L"");       /* { dg-warning "nul past the end" } */
+  T (1, "%ls",      L"");
+  T (1, "%ls",      L"\0");
+  T (1, "%1ls",     L"");       /* { dg-warning "nul past the end" } */
+
+  T (0, "%*ls",  0, L"");       /* { dg-warning "nul past the end" } */
+  T (1, "%*ls",  0, L"");
+  T (1, "%*ls",  0, L"\0");
+  T (1, "%*ls",  1, L"");       /* { dg-warning "nul past the end" } */
+
+  T (1, "%ls",      L"1");      /* { dg-warning "nul past the end" } */
+  T (1, "%.0ls",    L"1");
+  T (2, "%.0ls",    L"1");
+  T (2, "%.1ls",    L"1");
+
+  /* The "%.2ls" directive below will write at a minimum 1 byte (because
+     L"1" is known and can be assumed to convert to at least one multibyte
+     character), and at most 2 bytes because of the precision.  Since its
+     output is explicitly bounded it is diagnosed.  */
+  T (2, "%.2ls",    L"1");      /* { dg-warning "nul past the end" } */
+  T (2, "%.*ls", 2, L"1");      /* { dg-warning "nul past the end" } */
+
+  /* The following three are constrained by the precision to at most
+     that many bytes of the converted wide string plus a terminating NUL.  */
+  T (2, "%.0ls",    L"1");
+  T (2, "%.1ls",    L"1");
+  T (3, "%.2ls",    L"1");
+}
+
+
+struct Arrays {
+  char a1 [1];
+  char a2 [2];
+  char a3 [3];
+  char a4 [4];
+  char a0 [0];
+  char ax [];
+};
+
+/* Exercise buffer overflow detection with non-const string arguments.  */
+
+void test_s_nonconst (const char *s, const wchar_t *ws, struct Arrays *a)
+{
+  T (0, "%s",   s);             /* { dg-warning "into a region" "sprintf transformed into strcpy" { xfail *-*-*-* } } */
+  T (1, "%s",   s);             /* { dg-warning "nul past the end" "sprintf transformed into strcpy" { xfail *-*-*-* } } */
+  T (1, "%1s",  s);             /* { dg-warning "nul past the end" } */
+  T (1, "%.0s", s);
+  T (1, "%.1s", s);             /* { dg-warning "writing a terminating nul" } */
+
+  T (1, "%ls",  ws);            /* { dg-warning "writing a terminating nul" } */
+
+  /* Verify that the size of the array is used in lieu of its length.
+     The minus sign disables GCC's sprintf to strcpy transformation.  */
+  T (1, "%-s", a->a1);          /* { dg-warning "nul past the end" } */
+
+  /* In the following test, since the length of the strings isn't known,
+     their type (the array) is used to bound the maximum length to 1,
+     which means the "%-s" directive would not overflow the buffer,
+     but it would leave no room for the terminating nul.  */
+  T (1, "%-s", a->a2);          /* { dg-warning "writing a terminating nul" } */
+
+  /* Unlike in the test above, since the length of the string is bounded
+     by the array type to at most 2, the "^-s" directive is diagnosed firts,
+     preventing the diagnostic about the terminatinb nul.  */
+  T (1, "%-s", a->a3);          /* { dg-warning "directive writing between 1 and 2 bytes" } */
+
+  /* The length of a zero length array and flexible array member is
+     unknown and at leve 2 assumed to be at least 1.  */
+  T (1, "%-s", a->a0);          /* { dg-warning "nul past the end" } */
+  T (1, "%-s", a->ax);          /* { dg-warning "nul past the end" } */
+
+  T (2, "%-s", a->a0);
+  T (2, "%-s", a->ax);
+}
+
+  /* Exercise buffer overflow detection with non-const integer arguments.  */
+
+void test_hh_nonconst (int x)
+{
+  T (1, "%hhi",         x);     /* { dg-warning "into a region" } */
+  T (2, "%hhi",         x);     /* { dg-warning "into a region" } */
+  T (3, "%hhi",         x);     /* { dg-warning "into a region" } */
+  T (4, "%hhi",         x);     /* { dg-warning "may write a terminating nul past the end of the destination" } */
+}
+
+void test_h_nonconst (int x)
+{
+  extern UChar uc;
+
+  T (1, "%hi",         uc);     /* { dg-warning "into a region" } */
+  T (2, "%hi",         uc);     /* { dg-warning "into a region" } */
+  /* Formatting an 8-bit unsigned char as a signed short (or any other
+     type with greater precision) can write at most 3 characters.  */
+  T (3, "%hi",         uc);     /* { dg-warning "terminating nul past" } */
+  T (4, "%hi",         uc);
+
+  /* Verify that the same thing works when the int argument is cast
+     to unsigned char.  */
+  T (1, "%hi",   (UChar)x);     /* { dg-warning "into a region" } */
+  T (2, "%hi",   (UChar)x);     /* { dg-warning "into a region" } */
+  T (3, "%hi",   (UChar)x);     /* { dg-warning "may write a terminating nul past the end of the destination" } */
+  T (4, "%hi",   (UChar)x);
+}
+
+void test_i_nonconst (int x)
+{
+  extern UChar uc;
+
+  T (1, "%i",          uc);     /* { dg-warning "into a region" } */
+  T (2, "%i",          uc);     /* { dg-warning "into a region" } */
+  T (3, "%i",          uc);     /* { dg-warning "terminating nul past" } */
+  T (4, "%i",          uc);
+
+  T (1, "%i",    (UChar)x);     /* { dg-warning "into a region" } */
+  T (2, "%i",    (UChar)x);     /* { dg-warning "into a region" } */
+  T (3, "%i",    (UChar)x);     /* { dg-warning "terminating nul past" } */
+  T (4, "%i",    (UChar)x);
+
+  /* Verify the same thing using a bit-field.  */
+  extern struct {
+    unsigned int  b1: 1;
+    unsigned int  b2: 2;
+    unsigned int  b3: 3;
+    unsigned int  b4: 4;
+             int sb4: 4;
+    unsigned int  b5: 5;
+    unsigned int  b6: 6;
+    unsigned int  b7: 7;
+    unsigned int  b8: 8;
+  } bf, abf[], *pbf;
+
+  T (1, "%i",       bf.b1);     /* { dg-warning "nul past the end" } */
+  T (1, "%i",  abf [x].b1);     /* { dg-warning "nul past the end" } */
+  T (1, "%i",     pbf->b1);     /* { dg-warning "nul past the end" } */
+  /* A one bit bit-field can only be formatted as '0' or '1'.  Similarly,
+     two- and three-bit bit-fields can only be formatted as a single
+     decimal digit.  */
+  T (2, "%i",       bf.b1);
+  T (2, "%i",  abf [x].b1);
+  T (2, "%i",     pbf->b1);
+  T (2, "%i",       bf.b2);
+  T (2, "%i",  abf [x].b2);
+  T (2, "%i",     pbf->b2);
+  T (2, "%i",       bf.b3);
+  T (2, "%i",  abf [x].b3);
+  T (2, "%i",     pbf->b3);
+  /* A four-bit bit-field can be formatted as either one or two digits.  */
+  T (2, "%i",       bf.b4);     /* { dg-warning "nul past the end" } */
+  T (2, "%i",  abf [x].b4);     /* { dg-warning "nul past the end" } */
+  T (2, "%i",     pbf->b4);     /* { dg-warning "nul past the end" } */
+
+  T (3, "%i",       bf.b4);
+  T (3, "%i",     pbf->b4);
+  T (3, "%i",       bf.b5);
+  T (3, "%i",     pbf->b5);
+  T (3, "%i",       bf.b6);
+  T (3, "%i",     pbf->b6);
+  T (3, "%i",       bf.b7);     /* { dg-warning "nul past the end" } */
+  T (3, "%i",     pbf->b7);     /* { dg-warning "nul past the end" } */
+
+  T (1, "%i",       bf.b8);     /* { dg-warning "into a region" } */
+  T (2, "%i",       bf.b8);     /* { dg-warning "into a region" } */
+  /* Formatting an 8-bit unsigned char as a signed short (or any other
+     type with greater precision) int can write at most 3 characters.  */
+  T (3, "%i",       bf.b8);     /* { dg-warning "terminating nul past" } */
+  T (4, "%i",       bf.b8);
+
+  T (1, "%i",       bf.b8);     /* { dg-warning "into a region" } */
+  T (2, "%i",       bf.b8);     /* { dg-warning "into a region" } */
+  T (3, "%i",       bf.b8);     /* { dg-warning "terminating nul past" } */
+
+  T (2, "%i",      bf.sb4);     /* { dg-warning "terminating nul past" } */
+  T (3, "%i",      bf.sb4);
+  T (4, "%i",      bf.sb4);
+}
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c
new file mode 100644
index 0000000..e63e9e9
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-3.c
@@ -0,0 +1,234 @@ 
+/* { dg-do compile } */
+/* { dg-options "-std=c99 -O2 -Wformat -Wformat-length=1 -ftrack-macro-expansion=0" } */
+
+#ifndef LINE
+#  define LINE 0
+#endif
+
+#define bos(x) __builtin_object_size (x, 0)
+
+#define T(bufsize, fmt, ...)						\
+    do {								\
+      if (!LINE || __LINE__ == LINE)					\
+	{								\
+	  char *d = (char *)__builtin_malloc (bufsize);			\
+	  __builtin___sprintf_chk (d, 0, bos (d), fmt, __VA_ARGS__);	\
+	  sink (d);							\
+	}								\
+    } while (0)
+
+void
+sink (void*);
+
+/* Identity function to verify that the checker figures out the value
+   of the operand even when it's not constant (i.e., makes use of
+   inlining and constant propagation information).  */
+
+int i (int x) { return x; }
+const char* s (const char *str) { return str; }
+
+/* Function to "generate" a unique unknown number (as far as GCC can
+   tell) each time it's called.  It prevents the optimizer from being
+   able to narrow down the ranges of possible values in test functions
+   with repeated references to the same variable.  */
+extern int x (void);
+
+/* Verify that the checker can detect buffer overflow when the "%s"
+   argument is in a known range of lengths and one or both of which
+   exceed the size of the destination.  */
+
+void test_sprintf_chk_string (const char *s, const char *t)
+{
+#define x x ()
+
+  T (1, "%s", x ? "" : "1");       /* { dg-warning "nul past the end" } */
+  T (1, "%s", x ? "1" : "");       /* { dg-warning "nul past the end" } */
+  T (1, "%s", x ? s : "1");        /* { dg-warning "nul past the end" } */
+  T (1, "%s", x ? "1" : s);        /* { dg-warning "nul past the end" } */
+  T (1, "%s", x ? s : t);
+
+  T (2, "%s", x ? "" : "1");
+  T (2, "%s", x ? "" : s);
+  T (2, "%s", x ? "1" : "");
+  T (2, "%s", x ? s : "");
+  T (2, "%s", x ? "1" : "2");
+  T (2, "%s", x ? "" : "12");      /* { dg-warning "nul past the end" } */
+  T (2, "%s", x ? "12" : "");      /* { dg-warning "nul past the end" } */
+
+  T (2, "%s", x ? "" : "123");     /* { dg-warning "into a region" } */
+  T (2, "%s", x ? "123" : "");     /* { dg-warning "into a region" } */
+
+#undef x
+}
+
+
+/* Verify that the checker makes use of integer constant propagation
+   to detect buffer overflow in non-constant cases.  */
+
+void test_sprintf_chk_integer_value (void)
+{
+  T ( 1, "%i",  i (    0));         /* { dg-warning "nul past the end" } */
+  T ( 1, "%i",  i (    1));         /* { dg-warning "nul past the end" } */
+  T ( 1, "%i",  i (   -1));         /* { dg-warning "into a region" } */
+  T ( 1, "%i_", i (    1));         /* { dg-warning "character ._. at offset 2 past the end" } */
+  T ( 1, "_%i", i (    1));         /* { dg-warning "into a region" } */
+  T ( 1, "_%i_",i (    1));         /* { dg-warning "into a region" } */
+  T ( 1, "%o",  i (    0));         /* { dg-warning "nul past the end" } */
+  T ( 1, "%u",  i (    0));         /* { dg-warning "nul past the end" } */
+  T ( 1, "%x",  i (    0));         /* { dg-warning "nul past the end" } */
+  T ( 1, "%#x", i (    0));         /* { dg-warning "nul past the end" } */
+  T ( 1, "%x",  i (    1));         /* { dg-warning "nul past the end" } */
+  T ( 1, "%#x", i (    1));         /* { dg-warning "into a region" } */
+
+  T ( 2, "%i",  i (    0));
+  T ( 2, "%i",  i (    1));
+  T ( 2, "%i",  i (    9));
+  T ( 2, "%i",  i (   -1));         /* { dg-warning "nul past the end" } */
+  T ( 2, "%i",  i (   10));         /* { dg-warning "nul past the end" } */
+  T ( 2, "%i_", i (    0));         /* { dg-warning "nul past the end" } */
+  T ( 2, "_%i", i (    0));         /* { dg-warning "nul past the end" } */
+  T ( 2, "_%i_",i (    0));         /* { dg-warning "character ._. at offset 3 past the end" } */
+  T ( 2, "%o",  i (    1));
+  T ( 2, "%o",  i (    7));
+  T ( 2, "%o",  i (  010));         /* { dg-warning "nul past the end" } */
+  T ( 2, "%o",  i ( 0100));         /* { dg-warning "into a region" } */
+  T ( 2, "%x",  i (    1));
+  T ( 2, "%#x", i (    1));         /* { dg-warning "into a region" } */
+  T ( 2, "%x",  i (  0xa));
+  T ( 2, "%x",  i (  0xf));
+  T ( 2, "%x",  i ( 0x10));         /* { dg-warning "nul past the end" } */
+  T ( 2, "%x",  i ( 0xff));         /* { dg-warning "nul past the end" } */
+  T ( 2, "%x",  i (0x1ff));         /* { dg-warning "into a region" } */
+
+  T ( 3, "%i",  i (    0));
+  T ( 3, "%i",  i (    1));
+  T ( 3, "%i",  i (    9));
+  T ( 3, "%i",  i (   -9));
+  T ( 3, "%i",  i (   10));
+  T ( 3, "%i",  i (   99));
+  T ( 3, "%i",  i (  -99));         /* { dg-warning "nul past the end" } */
+
+  T ( 3, "%i",  i (99) + i (1));    /* { dg-warning "nul past the end" } */
+
+  T ( 8, "%8u", i (    1));         /* { dg-warning "nul past the end" } */
+  T ( 9, "%8u", i (    1));
+}
+
+/* Functions to require optimization to figure out the range of the operand.
+   Used to verify that the checker makes use of the range information to
+   avoid diagnosing the output of sufficiently constrained arguments to
+   integer directives.  */
+
+signed char*
+range_schar (signed char *val, signed char min, signed char max)
+{
+  if (*val < min || max < *val) __builtin_abort ();
+  return val;
+}
+
+unsigned char*
+range_uchar (unsigned char *val, unsigned char min, unsigned char max)
+{
+  if (*val < min || max < *val) __builtin_abort ();
+  return val;
+}
+
+signed short*
+range_sshort (signed short *val, signed short min, signed short max)
+{
+  if (*val < min || max < *val) __builtin_abort ();
+  return val;
+}
+
+unsigned short*
+range_ushort (unsigned short *val, unsigned short min, unsigned short max)
+{
+  if (*val < min || max < *val) __builtin_abort ();
+  return val;
+}
+
+/* Helper to prevent GCC from figuring out the return value.  */
+extern int idx (void);
+
+/* 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)
+{
+  (void)&a;
+
+  /* Ra creates a range of signed char for A [idx].  A different
+     value is used each time to prevent the ranges from intesecting
+     one another, possibly even eliminating some tests as a result
+     of the range being empty. */
+#define R(min, max) *range_schar (a + idx (), min, max)
+
+  T ( 0, "%i",  R (0, 9));      /* { dg-warning ".%i. directive writing 1 byte into a region of size 0" } */
+  T ( 1, "%i",  R (0, 9));      /* { dg-warning "nul past the end" } */
+  T ( 2, "%i",  R (0, 9));
+  T ( 2, "%i",  R (-1, 0));     /* { dg-warning "may write a terminating nul past the end of the destination" } */
+  T ( 2, "%i",  R (9, 10));     /* { dg-warning "may write a terminating nul past the end of the destination" } */
+
+  T ( 3, "%i",  R ( -9,   9));
+  T ( 3, "%i",  R (-99,  99));  /* { dg-warning "may write a terminating nul past the end of the destination" } */
+  T ( 3, "%i",  R (  0,  99));
+  T ( 3, "%i",  R (  0, 100));  /* { dg-warning "may write a terminating nul past the end of the destination" } */
+
+  /* The following call may write as few as 3 bytes and as many as 5.
+     It's judgment call how best to diagnose it to make the potential
+     problem clear.  */
+  T ( 3, "%i%i", R (1, 10), R (9, 10));   /* { dg-warning ".%i. directive writing between 1 and 2 bytes into a region of size 1" } */
+
+  T ( 4, "%i%i", R (10, 11), R (12, 13));   /* { dg-warning "nul past the end" } */
+
+  T ( 5, "%i%i", R (-9, 99), R (-9, 99));
+
+  T ( 6, "%i_%i_%i", R (0, 9), R (0, 9), R (0,  9));
+  T ( 6, "%i_%i_%i", R (0, 9), R (0, 9), R (0, 10));  /* { dg-warning "may write a terminating nul past the end" } */
+  T ( 6, "%i_%i_%i", R (0, 9), R (0, 10), R (0, 9));  /* { dg-warning "may write a terminating nul past the end" } */
+  T ( 6, "%i_%i_%i", R (0, 10), R (0, 9), R (0, 9));  /* { dg-warning "may write a terminating nul past the end" } */
+  T ( 6, "%i_%i_%i", R (0, 9), R (0, 10), R (0, 10)); /* { dg-warning ".%i. directive writing between 1 and 2 bytes into a region of size 1" } */
+}
+
+void test_sprintf_chk_range_uchar (unsigned char *a, unsigned char *b)
+{
+  (void)&a;
+  (void)&b;
+
+#undef Ra
+#define Ra(min, max) *range_uchar (a + idx (), min, max)
+
+  T ( 0, "%i",  Ra (0,  9));   /* { dg-warning ".%i. directive writing 1 byte into a region of size 0" } */
+  T ( 1, "%i",  Ra (0,  9));   /* { dg-warning "nul past the end" } */
+  T ( 2, "%i",  Ra (0,  9));
+  T ( 2, "%i",  Ra (9, 10));   /* { dg-warning "may write a terminating nul past the end of the destination" } */
+
+  T ( 3, "%i",  Ra (0,  99));
+  T ( 3, "%i",  Ra (0, 100));  /* { dg-warning "may write a terminating nul past the end of the destination" } */
+}
+
+void test_sprintf_chk_range_sshort (signed short *a, signed short *b)
+{
+  (void)&a;
+  (void)&b;
+
+#undef Ra
+#define Ra(min, max) *range_sshort (a + idx (), min, max)
+
+  T ( 0, "%i",  Ra ( 0, 9));     /* { dg-warning ".%i. directive writing 1 byte into a region of size 0" } */
+  T ( 1, "%i",  Ra ( 0, 1));     /* { dg-warning "nul past the end" } */
+  T ( 1, "%i",  Ra ( 0, 9));     /* { dg-warning "nul past the end" } */
+  T ( 2, "%i",  Ra ( 0, 1));
+  T ( 2, "%i",  Ra ( 8, 9));
+  T ( 2, "%i",  Ra ( 0, 9));
+  T ( 2, "%i",  Ra (-1, 0));     /* { dg-warning "may write a terminating nul past the end of the destination" } */
+  T ( 2, "%i",  Ra ( 9, 10));    /* { dg-warning "may write a terminating nul past the end of the destination" } */
+
+  T ( 3, "%i",  Ra ( 0, 99));
+  T ( 3, "%i",  Ra (99, 999));   /* { dg-warning "may write a terminating nul past the end of the destination" } */
+
+  T ( 4, "%i",  Ra (  0,  999));
+  T ( 4, "%i",  Ra ( 99,  999));
+  T ( 4, "%i",  Ra (998,  999));
+  T ( 4, "%i",  Ra (999, 1000)); /* { dg-warning "may write a terminating nul past the end of the destination" } */
+}
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf.c
new file mode 100644
index 0000000..7ab9fd7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf.c
@@ -0,0 +1,430 @@ 
+/* Test to verify that the return value of calls to __builtin_sprintf
+   that produce a known number of bytes on output is available for
+   constant folding.  With optimization enable the test will fail to
+   link if any of the assertions fails.  Without optimization the test
+   aborts at runtime if any of the assertions fails.  */
+/* { dg-do run } */
+/* { dg-additional-options "-O2 -Wno-pedantic -fprintf-return-value" } */
+
+#ifndef LINE
+#  define LINE   0
+#endif
+
+#if __STDC_VERSION__ < 199901L
+#  define __func__   __FUNCTION__
+#endif
+
+typedef __SIZE_TYPE__ size_t;
+
+unsigned ntests;
+unsigned nfails;
+
+void __attribute__ ((noclone, noinline))
+checkv (const char *func, int line, int res, char *dst, const char *fmt,
+	__builtin_va_list va)
+{
+  int n = __builtin_vsprintf (dst, fmt, va);
+  size_t len = __builtin_strlen (dst);
+
+  ++ntests;
+
+  int fail = 0;
+  if (res != n)
+    {
+      __builtin_printf ("FAIL: %s:%i: \"%s\" expected result for \"%s\" "
+			"doesn't match function call return value: %i != %i\n",
+			func, line, fmt, dst, res, n);
+      fail = 1;
+    }
+  else
+    {
+      __builtin_printf ("PASS: %s:%i: \"%s\" result %i: \"%s\"\n",
+			func, line, fmt, res, dst);
+
+      if ((size_t)res != len)
+	{
+	  __builtin_printf ("FAIL: %s:%i: \"%s\" expected result for \"%s\" "
+			    "doesn't match output length: %i != %zu\n",
+			    func, line, fmt, dst, res, len);
+	  fail = 1;
+	}
+    }
+
+  if (fail)
+    ++nfails;
+}
+
+void __attribute__ ((noclone, noinline))
+check (const char *func, int line, int res, char *dst, const char *fmt, ...)
+{
+  __builtin_va_list va;
+  __builtin_va_start (va, fmt);
+  checkv (func, line, res, dst, fmt, va);
+  __builtin_va_end (va);
+}
+
+char buffer[256];
+char* volatile dst = buffer;
+
+#define concat(a, b)   a ## b
+#define CAT(a, b)      concat (a, b)
+
+#if __OPTIMIZE__
+/* With optimization references to the following undefined symbol which
+   is unique for each test case are expected to be eliminated.  */
+#  define TEST_FAILURE(line, res, n)					\
+  do {									\
+    extern void CAT (failure_on_line_, line)(void);			\
+    CAT (failure_on_line_, line)();					\
+  } while (0)
+#else
+/* The test is run by DejaGnu with optimization enabled.  When it's run
+   with it disabled (i.e., at -O0) each test case is verified at runtime
+   and the test aborts just before exiting if any of them failed.  */
+#  define TEST_FAILURE(line, res, n)					\
+  __builtin_printf ("FAIL: %s:%i: expected %i, got %i\n",		\
+		    __func__, line, res, n)
+#endif
+
+#define T(res, fmt, ...)						\
+  if (!LINE || LINE == __LINE__)					\
+    do {								\
+      int n = __builtin_sprintf (buffer + sizeof buffer - res - 1,	\
+				 fmt, __VA_ARGS__);			\
+      if (res != n)							\
+	{								\
+	  TEST_FAILURE (__LINE__, res, n);				\
+	}								\
+      check (__func__, __LINE__, res, dst, fmt, __VA_ARGS__);		\
+    } while (0)
+
+
+static void __attribute__ ((noinline, noclone))
+test_c (char c)
+{
+  T (1, "%c",       '1');
+  T (1, "%c",       c);
+  T (2, "%2c",      c);
+  T (2, "%c%c",     '1', '2');
+  T (3, "%3c",      c);
+  T (3, "%c%c%c",   '1', '2', '3');
+  T (4, "%c%c %c",  '1', '2', '3');
+  T (5, "%c %c %c", '1', '2', '3');
+  T (5, "%c %c %c",   c,   c,   c);
+}
+
+/* Generate a pseudo-random value in the specified range.  The return
+   value must be unsigned char to work around limitations in the GCC
+   range information.  Similarly for the declaration of rand() whose
+   correct return value should be int, but that also prevents the range
+   information from making it to the printf pass.  */
+
+unsigned char uchar_range (unsigned min, unsigned max)
+{
+  extern unsigned rand (void);
+
+  unsigned x;
+  x = rand ();
+
+  if (x < min)
+    x = min;
+  else if (max < x)
+    x = max;
+
+  return x;
+}
+
+static void __attribute__ ((noinline, noclone))
+test_d_i (int i, long li)
+{
+  T ( 1, "%d",            0);
+  T ( 2, "%d%d",          0,   1);
+  T ( 3, "%d%d",          9,  10);
+  T ( 4, "%d%d",         11,  12);
+  T ( 5, "%d:%d",        12,  34);
+  T ( 5, "%d",           12345);
+  T ( 6, "%d",          -12345);
+  T (15, "%d:%d:%d:%d", 123, 124, 125, 126);
+
+  T ( 1, "%i", uchar_range (0, 9));
+
+  /* The range information available to passes other than the Value
+     Range Propoagation pass itself is so bad that the following two
+     tests fail (the range seen in the test below is [0, 99] rather
+     than [10, 99].
+  T ( 2, "%i", uchar_range (10, 99));
+  T ( 3, "%i", uchar_range (100, 199));
+  */
+
+#if __SIZEOF_INT__ == 2
+  T ( 6, "%6d",      i);
+  T ( 6, "%+6d",     i);
+  T ( 6, "%-6d",     i);
+  T ( 6, "%06d",     i);
+#elif __SIZEOF_INT__ == 4
+  T (11, "%11d",     i);
+  T (11, "%+11d",    i);
+  T (11, "%-11d",    i);
+  T (11, "%011d",    i);
+#elif __SIZEOF_INT__ == 8
+  T (20, "%20d",     i);
+  T (20, "%+20d",    i);
+  T (20, "%-29d",    i);
+  T (20, "%020d",    i);
+#endif
+
+#if __SIZEOF_LONG__ == 2
+  T ( 6, "%6ld",      li);
+  T ( 6, "%+6ld",     li);
+  T ( 6, "%-6ld",     li);
+  T ( 6, "%06ld",     li);
+#elif __SIZEOF_LONG__ == 4
+  T (11, "%11ld",     li);
+  T (11, "%+11ld",    li);
+  T (11, "%-11ld",    li);
+  T (11, "%011ld",    li);
+#elif __SIZEOF_LONG__ == 8
+  T (20, "%20ld",     li);
+  T (20, "%+20ld",    li);
+  T (20, "%-20ld",    li);
+  T (20, "%020ld",    li);
+#endif
+}
+
+static void __attribute__ ((noinline, noclone))
+test_x (unsigned char uc, unsigned short us, unsigned ui)
+{
+  T ( 1, "%hhx",          0);
+  T ( 2, "%2hhx",         0);
+  T ( 2, "%02hhx",        0);
+  T ( 2, "%#02hhx",       0);
+
+  T ( 1, "%hhx",          1);
+  T ( 2, "%2hhx",         1);
+  T ( 2, "%02hhx",        1);
+  T ( 3, "%#02hhx",       1);
+
+  T ( 2, "%2hhx",        uc);
+  T ( 2, "%02hhx",       uc);
+  T ( 5, "%#05hhx",      uc);
+
+  T ( 2, "%2hhx",        us);
+  T ( 2, "%02hhx",       us);
+  T ( 5, "%#05hhx",      us);
+
+  T ( 2, "%2hhx",        ui);
+  T ( 2, "%02hhx",       ui);
+  T ( 5, "%#05hhx",      ui);
+
+  T ( 1, "%x",            0);
+  T ( 1, "%#x",           0);
+  T ( 1, "%#0x",          0);
+  T ( 1, "%x",            1);
+  T ( 1, "%x",          0xf);
+  T ( 2, "%x",         0x10);
+  T ( 2, "%x",         0xff);
+  T ( 3, "%x",        0x100);
+
+  T (11, "%02x:%02x:%02x:%02x",         0xde, 0xad, 0xbe, 0xef);
+
+  /* The following would be optimized if the range information of
+  the variable's type was made available.  Alas, it's lost due
+  to the promotion of the actual argument (unsined char) to
+  the type of the "formal" argument (int in the case of the
+  ellipsis).
+  T (11, "%02x:%02x:%02x:%02x",   uc,   uc,   uc,   uc);
+  */
+  T (11, "%02hhx:%02hhx:%02hhx:%02hhx",   uc,   uc,   uc,   uc);
+
+#if __SIZEOF_SHORT__ == 2
+  T ( 4, "%04hx",                   us);
+  T ( 9, "%04hx:%04hx",             us, us);
+  T (14, "%04hx:%04hx:%04hx",       us, us, us);
+  T (19, "%04hx:%04hx:%04hx:%04hx", us, us, us, us);
+#endif
+
+#if __SIZEOF_INT__ == 2
+  T ( 4, "%04x", ui);
+  T ( 6, "%#06x", ui);
+#elif __SIZEOF_INT__ == 4
+  T ( 8, "%08x", ui);
+  T (10, "%#010x", ui);
+#elif __SIZEOF_INT__ == 8
+  T (16, "%016x", ui);
+  T (18, "%#018x",  ui);
+#endif
+}
+
+static void __attribute__ ((noinline, noclone))
+test_a_double ()
+{
+  T ( 6, "%a",   0.0);        /* 0x0p+0 */
+  T ( 6, "%a",   1.0);        /* 0x8p-3 */
+  T ( 6, "%a",   2.0);        /* 0x8p-2 */
+
+  T ( 8, "%.1a", 3.0);        /* 0xc.0p-2 */
+  T ( 9, "%.2a", 4.0);        /* 0xa.00p-1 */
+}
+
+static void __attribute__ ((noinline, noclone))
+test_a_long_double ()
+{
+  T ( 6, "%La",   0.0L);      /* 0x0p+0 */
+  T ( 6, "%La",   1.0L);      /* 0x8p-3 */
+  T ( 6, "%La",   2.0L);      /* 0x8p-2 */
+
+  T ( 8, "%.1La", 3.0L);      /* 0xc.0p-2 */
+  T ( 9, "%.2La", 4.0L);      /* 0xa.00p-1 */
+}
+
+static void __attribute__ ((noinline, noclone))
+test_e_double ()
+{
+  T (12, "%e",  1.0e0);
+  T (13, "%e", -1.0e0);
+  T (12, "%e",  1.0e+1);
+  T (13, "%e", -1.0e+1);
+  T (12, "%e",  1.0e+12);
+  T (13, "%e", -1.0e+12);
+  T (13, "%e",  1.0e+123);
+  T (14, "%e", -1.0e+123);
+
+  T (12, "%e",  9.999e+99);
+  T (12, "%e",  9.9999e+99);
+  T (12, "%e",  9.99999e+99);
+
+  /* The actual output of the following directive depends on the rounding
+     mode.  */
+  /* T (12, "%e",  9.9999994e+99); */
+
+  T (12, "%e",  1.0e-1);
+  T (12, "%e",  1.0e-12);
+  T (13, "%e",  1.0e-123);
+}
+
+static void __attribute__ ((noinline, noclone))
+test_e_long_double ()
+{
+  T (12, "%Le",  1.0e0L);
+  T (13, "%Le", -1.0e0L);
+  T (12, "%Le",  1.0e+1L);
+  T (13, "%Le", -1.0e+1L);
+  T (12, "%Le",  1.0e+12L);
+  T (13, "%Le", -1.0e+12L);
+  T (13, "%Le",  1.0e+123L);
+  T (14, "%Le", -1.0e+123L);
+
+  T (12, "%Le",  9.999e+99L);
+  T (12, "%Le",  9.9999e+99L);
+  T (12, "%Le",  9.99999e+99L);
+  T (12, "%Le",  9.999999e+99L);
+
+  /* The actual output of the following directive depends on the rounding
+     mode.  */
+  /* T (12, "%Le",  9.9999994e+99L); */
+
+  T (12, "%Le",  1.0e-1L);
+  T (12, "%Le",  1.0e-12L);
+  T (13, "%Le",  1.0e-123L);
+
+  T ( 6, "%.0Le",   1.0e-111L);
+  T ( 8, "%.1Le",   1.0e-111L);
+  T (19, "%.12Le",  1.0e-112L);
+  T (20, "%.13Le",  1.0e-113L);
+}
+
+static void __attribute__ ((noinline, noclone))
+test_f_double ()
+{
+  T (  8, "%f", 0.0e0);
+  T (  8, "%f", 0.1e0);
+  T (  8, "%f", 0.12e0);
+  T (  8, "%f", 0.123e0);
+  T (  8, "%f", 0.1234e0);
+  T (  8, "%f", 0.12345e0);
+  T (  8, "%f", 0.123456e0);
+  T (  8, "%f", 1.234567e0);
+
+  T (  9, "%f", 1.0e+1);
+  T ( 20, "%f", 1.0e+12);
+  T (130, "%f", 1.0e+123);
+
+  T (  8, "%f", 1.0e-1);
+  T (  8, "%f", 1.0e-12);
+  T (  8, "%f", 1.0e-123);
+}
+
+static void __attribute__ ((noinline, noclone))
+test_f_long_double ()
+{
+  T (  8, "%Lf", 0.0e0L);
+  T (  8, "%Lf", 0.1e0L);
+  T (  8, "%Lf", 0.12e0L);
+  T (  8, "%Lf", 0.123e0L);
+  T (  8, "%Lf", 0.1234e0L);
+  T (  8, "%Lf", 0.12345e0L);
+  T (  8, "%Lf", 0.123456e0L);
+  T (  8, "%Lf", 1.234567e0L);
+
+  T (  9, "%Lf", 1.0e+1L);
+  T ( 20, "%Lf", 1.0e+12L);
+  T (130, "%Lf", 1.0e+123L);
+
+  T (  8, "%Lf", 1.0e-1L);
+  T (  8, "%Lf", 1.0e-12L);
+  T (  8, "%Lf", 1.0e-123L);
+}
+
+static void __attribute__ ((noinline, noclone))
+test_s (void)
+{
+  T (  0, "%s", "");
+  T (  0, "%s", "\0");
+  T (  1, "%1s", "");
+  T (  1, "%s", "1");
+  T (  2, "%2s", "");
+  T (  2, "%s", "12");
+  T (  2, "%s%s", "12", "");
+  T (  2, "%s%s", "", "12");
+  T (  2, "%s%s", "1", "2");
+  T (  3, "%3s", "");
+  T (  3, "%3s", "1");
+  T (  3, "%3s", "12");
+  T (  3, "%3s", "123");
+  T (  3, "%3.3s", "1");
+  T (  3, "%3.3s", "12");
+  T (  3, "%3.3s", "123");
+  T (  3, "%3.3s", "1234");
+  T (  3, "%3.3s", "12345");
+  T (  3, "%s %s", "1", "2");
+  T (  4, "%s %s", "12", "3");
+  T (  5, "%s %s", "12", "34");
+  T (  5, "[%s %s]", "1", "2");
+  T (  6, "[%s %s]", "12", "3");
+  T (  7, "[%s %s]", "12", "34");
+}
+
+int main ()
+{
+  test_c ('a');
+  test_d_i (0xdeadbeef, 0xdeadbeefL);
+  test_x ('a', 0xdead, 0xdeadbeef);
+
+  test_a_double ();
+  test_e_double ();
+  test_f_double ();
+
+  test_a_long_double ();
+  test_e_long_double ();
+  test_f_long_double ();
+
+  test_s ();
+
+  if (nfails)
+    {
+      __builtin_printf ("%u out of %u tests failed\n", nfails, ntests);
+      __builtin_abort ();
+    }
+
+  return 0;
+}
diff --git a/gcc/tree-pass.h b/gcc/tree-pass.h
index 36299a6..5728d3f 100644
--- a/gcc/tree-pass.h
+++ b/gcc/tree-pass.h
@@ -469,6 +469,7 @@  extern simple_ipa_opt_pass *make_pass_ipa_oacc (gcc::context *ctxt);
 extern simple_ipa_opt_pass *make_pass_ipa_oacc_kernels (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_gen_hsail (gcc::context *ctxt);
 extern gimple_opt_pass *make_pass_warn_nonnull_compare (gcc::context *ctxt);
+extern gimple_opt_pass *make_pass_sprintf_length (gcc::context *ctxt);
 
 /* IPA Passes */
 extern simple_ipa_opt_pass *make_pass_ipa_lower_emutls (gcc::context *ctxt);