diff mbox

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

Message ID 57D1B5F0.1030504@gmail.com
State New
Headers show

Commit Message

Martin Sebor Sept. 8, 2016, 7:03 p.m. UTC
Attached is another update to the patch to address the last round
of comments and suggestions, most notably to:

  *  implement the checking of the implementation limit of 4,095 on
     the output of a single directive to allow for the Glibc failure
     due to ENOMEM (the patch issues a warning and disables the
     optimization when this happens)
  *  implement checking for exceeding INT_MAX bytes (warn and disable
     optimization)
  *  call set_range_info when the return value optimization is not
     possible
  *  remove code to work around tree-optimization/71831 (now on
     trunk)

The -fprintf-return value optimization is still disabled.  GCC
successfully bootstraps with it and most tests pass but there's
a failure in the Fortran libgomp tests that I am yet to figure
out.

I'm hoping to get the patch reviewed and hopefully approved while
I debug the libgomp failure.

Martin

Comments

David Malcolm Sept. 8, 2016, 7:45 p.m. UTC | #1
On Thu, 2016-09-08 at 13:03 -0600, Martin Sebor wrote:
> Attached is another update to the patch to address the last round
> of comments and suggestions, most notably to:
> 
>   *  implement the checking of the implementation limit of 4,095 on
>      the output of a single directive to allow for the Glibc failure
>      due to ENOMEM (the patch issues a warning and disables the
>      optimization when this happens)
>   *  implement checking for exceeding INT_MAX bytes (warn and disable
>      optimization)
>   *  call set_range_info when the return value optimization is not
>      possible
>   *  remove code to work around tree-optimization/71831 (now on
>      trunk)
> 
> The -fprintf-return value optimization is still disabled.  GCC
> successfully bootstraps with it and most tests pass but there's
> a failure in the Fortran libgomp tests that I am yet to figure
> out.
> 
> I'm hoping to get the patch reviewed and hopefully approved while
> I debug the libgomp failure.
> 
> Martin

I see that you also integrated the substring_loc and format_warning API
into this revision - thanks.

The patch has a lot of macro-based testcases, presumably for exercising
all of the format codes and boundary conditions, but it seems to be
lacking what I'd call a "usability test case" - a test case that shows
a representative example of idiomatic but buggy code, along with the
full output of the warning, with carets and underlining, so that we can
easily see what the user experience is.  (sorry if there is one and I
didn't see it).

From a marketing point-of-view, I think any new diagnostics like this
deserve a screenshot on the website's gcc-7/changes.html page, showing
a simple example of the above that makes a casual reader think "gcc 7
looks neat; I've definitely made that mistake; I wonder if that's going
to find bugs in my code; I'd better try it at some point".

So please can you add a test case that demonstrates such a screenshot
-worthy example, using:

  /* { dg-options "-fdiagnostics-show-caret" } */

and you can use:

  /* { dg-begin-multiline-output "" }
copy&paste the source, underlines and carets here, omitting trailing dg
directives.
     { dg-end-multiline-output "" } */

(we'd strip away all the dg- directives when making the screenshots for
the website).

The act of creating such an example sometimes suggests tweaks e.g. to
the exact wording of the warning.

Sorry if this seems like I'm picking on you Martin; I just wanted to
share some thoughts that I'm trying to crystallize into general
guidelines on writing diagnostics.

Hope this is constructive
Dave
Martin Sebor Sept. 8, 2016, 9:58 p.m. UTC | #2
On 09/08/2016 01:45 PM, David Malcolm wrote:
> On Thu, 2016-09-08 at 13:03 -0600, Martin Sebor wrote:
>> Attached is another update to the patch to address the last round
>> of comments and suggestions, most notably to:
>>
>>    *  implement the checking of the implementation limit of 4,095 on
>>       the output of a single directive to allow for the Glibc failure
>>       due to ENOMEM (the patch issues a warning and disables the
>>       optimization when this happens)
>>    *  implement checking for exceeding INT_MAX bytes (warn and disable
>>       optimization)
>>    *  call set_range_info when the return value optimization is not
>>       possible
>>    *  remove code to work around tree-optimization/71831 (now on
>>       trunk)
>>
>> The -fprintf-return value optimization is still disabled.  GCC
>> successfully bootstraps with it and most tests pass but there's
>> a failure in the Fortran libgomp tests that I am yet to figure
>> out.
>>
>> I'm hoping to get the patch reviewed and hopefully approved while
>> I debug the libgomp failure.
>>
>> Martin
>
> I see that you also integrated the substring_loc and format_warning API
> into this revision - thanks.

Yes, I forgot to mention it among the highlights.  Thanks for making
the API available to the middle-end!

>
> The patch has a lot of macro-based testcases, presumably for exercising
> all of the format codes and boundary conditions, but it seems to be
> lacking what I'd call a "usability test case" - a test case that shows
> a representative example of idiomatic but buggy code, along with the
> full output of the warning, with carets and underlining, so that we can
> easily see what the user experience is.  (sorry if there is one and I
> didn't see it).

There is a simple test (in the test_sprintf_note() function in
builtin-sprintf-warn-1.c) that exercises the notes but there's always
room for more and more robust test cases :)  I'll see about adding
a few.

FWIW, the challenge here is not in adding them but rather in knowing
when to stop and what level of detail to go to.  With too many test
cases (exercising this level of detail) it can get very tedious and
time consuming to then change the diagnostics or add more detail.
This is not an excuse for not having enough tests.  But striking
the right balance between the level of detail in them is something
I had to grapple with on this project.

>  From a marketing point-of-view, I think any new diagnostics like this
> deserve a screenshot on the website's gcc-7/changes.html page, showing
> a simple example of the above that makes a casual reader think "gcc 7
> looks neat; I've definitely made that mistake; I wonder if that's going
> to find bugs in my code; I'd better try it at some point".
>
> So please can you add a test case that demonstrates such a screenshot
> -worthy example, using:
>
>    /* { dg-options "-fdiagnostics-show-caret" } */
>
> and you can use:
>
>    /* { dg-begin-multiline-output "" }
> copy&paste the source, underlines and carets here, omitting trailing dg
> directives.
>       { dg-end-multiline-output "" } */
>
> (we'd strip away all the dg- directives when making the screenshots for
> the website).
>
> The act of creating such an example sometimes suggests tweaks e.g. to
> the exact wording of the warning.
>
> Sorry if this seems like I'm picking on you Martin; I just wanted to
> share some thoughts that I'm trying to crystallize into general
> guidelines on writing diagnostics.

Not at all!  Thanks for the review and for the suggestion to make
use of the multiline DejaGnu directives.  I'll add more tests in
the next revision of the patch (as I have been in each iteration).

Martin
Joseph Myers Sept. 8, 2016, 10:10 p.m. UTC | #3
On Thu, 8 Sep 2016, Martin Sebor wrote:

> diff --git a/gcc/doc/tm.texi.in b/gcc/doc/tm.texi.in
> index da133a4..4607495 100644
> --- a/gcc/doc/tm.texi.in
> +++ b/gcc/doc/tm.texi.in
> @@ -4081,6 +4081,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

No, the substance of hook documentation should go in target.def with just 
an @hook line in tm.texi.in leading to the documentation going in tm.texi 
automatically.

You appear to be defining a target macro masquerading as a hook.  Please 
don't (new target macros should be avoided where possible); use a proper 
hook.  (Maybe the settings depending on OS rather than architecture means 
it needs to be one of those whose default is a manual setting in 
target-def.h rather than automatically generated, but that should be the 
limit of deviation from the normal workings of hooks.)

> +  const char *pfmt = TARGET_LIBC_PRINTF_POINTER_FORMAT (arg, &flags);

With a proper hook them you'd call targetm.libc_printf_pointer_format.

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

You need to use G_() around both format strings in such a case; xgettext 
doesn't know how to extract them both.
Jeff Law Sept. 16, 2016, 5 p.m. UTC | #4
On 09/08/2016 01:03 PM, Martin Sebor wrote:
> Attached is another update to the patch to address the last round
> of comments and suggestions, most notably to:
>
>  *  implement the checking of the implementation limit of 4,095 on
>     the output of a single directive to allow for the Glibc failure
>     due to ENOMEM (the patch issues a warning and disables the
>     optimization when this happens)
>  *  implement checking for exceeding INT_MAX bytes (warn and disable
>     optimization)
>  *  call set_range_info when the return value optimization is not
>     possible
>  *  remove code to work around tree-optimization/71831 (now on
>     trunk)
>
> The -fprintf-return value optimization is still disabled.  GCC
> successfully bootstraps with it and most tests pass but there's
> a failure in the Fortran libgomp tests that I am yet to figure
> out.
I'm just now getting back to this (sorry for the long delay).  I see 
there's another version after this one.  Did the version from 9/11/2016 
address the Fortran libgomp test issue?

jeff
Jan-Benedict Glaw Sept. 26, 2016, 10:08 p.m. UTC | #5
Hi Martin,

On Thu, 2016-09-08 13:03:12 -0600, Martin Sebor <msebor@gmail.com> wrote:
> Attached is another update to the patch to address the last round
> of comments and suggestions, most notably to:
[...]

with the currently committed version, the tic6x-uclinux target fails,
see ie.
http://toolchain.lug-owl.de/buildbot/show_build_details.php?id=630308 :

g++    -g -O2 -DIN_GCC  -DCROSS_DIRECTORY_STRUCTURE   -fno-exceptions -fno-rtti -fasynchronous-unwind-tables -W -Wall -Wno-narrowing -Wwrite-strings -Wcast-qual -Wmissing-format-attribute -Woverloaded-virtual -pedantic -Wno-long-long -Wno-variadic-macros -Wno-overlength-strings -fno-common  -DHAVE_CONFIG_H -static-libstdc++ -static-libgcc  -o cc1 c/c-lang.o c-family/stub-objc.o attribs.o c/c-errors.o c/c-decl.o c/c-typeck.o c/c-convert.o c/c-aux-info.o c/c-objc-common.o c/c-parser.o c/c-array-notation.o c/c-fold.o c-family/c-common.o c-family/c-cppbuiltin.o c-family/c-dump.o c-family/c-format.o c-family/c-gimplify.o c-family/c-indentation.o c-family/c-lex.o c-family/c-omp.o c-family/c-opts.o c-family/c-pch.o c-family/c-ppoutput.o c-family/c-pragma.o c-family/c-pretty-print.o c-family/c-semantics.o c-family/c-ada-spec.o c-family/c-cilkplus.o c-family/array-notation-common.o c-family/cilk.o c-family/c-ubsan.o default-c.o \
  cc1-checksum.o libbackend.a main.o libcommon-target.a libcommon.a ../libcpp/libcpp.a ../libdecnumber/libdecnumber.a libcommon.a ../libcpp/libcpp.a   ../libbacktrace/.libs/libbacktrace.a ../libiberty/libiberty.a ../libdecnumber/libdecnumber.a   -lmpc -lmpfr -lgmp -rdynamic -ldl  -L./../zlib -lz
c6x.o:(.data+0x778): undefined reference to `gnu_libc_printf_pointer_format(tree_node*, char const**)'
collect2: error: ld returned 1 exit status
/home/jbglaw/repos/gcc/gcc/c/Make-lang.in:84: recipe for target 'cc1' failed
make[1]: *** [cc1] Error 1
make[1]: Leaving directory '/home/jbglaw/build/tic6x-uclinux/build-gcc/gcc'
Makefile:4252: recipe for target 'all-gcc' failed
make: *** [all-gcc] Error 2


Add another one for uClibc?

MfG, JBG
Martin Sebor Sept. 27, 2016, 5:33 p.m. UTC | #6
On 09/26/2016 04:08 PM, Jan-Benedict Glaw wrote:
> Hi Martin,
>
> On Thu, 2016-09-08 13:03:12 -0600, Martin Sebor <msebor@gmail.com> wrote:
>> Attached is another update to the patch to address the last round
>> of comments and suggestions, most notably to:
> [...]
>
> with the currently committed version, the tic6x-uclinux target fails,
> see ie.
> http://toolchain.lug-owl.de/buildbot/show_build_details.php?id=630308 :
>
> g++    -g -O2 -DIN_GCC  -DCROSS_DIRECTORY_STRUCTURE   -fno-exceptions -fno-rtti -fasynchronous-unwind-tables -W -Wall -Wno-narrowing -Wwrite-strings -Wcast-qual -Wmissing-format-attribute -Woverloaded-virtual -pedantic -Wno-long-long -Wno-variadic-macros -Wno-overlength-strings -fno-common  -DHAVE_CONFIG_H -static-libstdc++ -static-libgcc  -o cc1 c/c-lang.o c-family/stub-objc.o attribs.o c/c-errors.o c/c-decl.o c/c-typeck.o c/c-convert.o c/c-aux-info.o c/c-objc-common.o c/c-parser.o c/c-array-notation.o c/c-fold.o c-family/c-common.o c-family/c-cppbuiltin.o c-family/c-dump.o c-family/c-format.o c-family/c-gimplify.o c-family/c-indentation.o c-family/c-lex.o c-family/c-omp.o c-family/c-opts.o c-family/c-pch.o c-family/c-ppoutput.o c-family/c-pragma.o c-family/c-pretty-print.o c-family/c-semantics.o c-family/c-ada-spec.o c-family/c-cilkplus.o c-family/array-notation-common.o c-family/cilk.o c-family/c-ubsan.o default-c.o \
>   cc1-checksum.o libbackend.a main.o libcommon-target.a libcommon.a ../libcpp/libcpp.a ../libdecnumber/libdecnumber.a libcommon.a ../libcpp/libcpp.a   ../libbacktrace/.libs/libbacktrace.a ../libiberty/libiberty.a ../libdecnumber/libdecnumber.a   -lmpc -lmpfr -lgmp -rdynamic -ldl  -L./../zlib -lz
> c6x.o:(.data+0x778): undefined reference to `gnu_libc_printf_pointer_format(tree_node*, char const**)'
> collect2: error: ld returned 1 exit status
> /home/jbglaw/repos/gcc/gcc/c/Make-lang.in:84: recipe for target 'cc1' failed
> make[1]: *** [cc1] Error 1
> make[1]: Leaving directory '/home/jbglaw/build/tic6x-uclinux/build-gcc/gcc'
> Makefile:4252: recipe for target 'all-gcc' failed
> make: *** [all-gcc] Error 2
>
>
> Add another one for uClibc?

Thanks.  There must be something wrong with the target hook I added.
This is a new area for me so please bear with me while I debug and
fix it.

Martin
Segher Boessenkool Sept. 28, 2016, 11:55 a.m. UTC | #7
On Tue, Sep 27, 2016 at 12:08:46AM +0200, Jan-Benedict Glaw wrote:
> Hi Martin,
> 
> On Thu, 2016-09-08 13:03:12 -0600, Martin Sebor <msebor@gmail.com> wrote:
> > Attached is another update to the patch to address the last round
> > of comments and suggestions, most notably to:
> [...]
> 
> with the currently committed version, the tic6x-uclinux target fails,
> see ie.
> http://toolchain.lug-owl.de/buildbot/show_build_details.php?id=630308 :

[ snip ]

The same happens for bfin-uclinux:

g++ -no-pie   -g -O2 -DIN_GCC  -DCROSS_DIRECTORY_STRUCTURE   -fno-exceptions -fno-rtti -fasynchronous-unwind-tables -W -Wall -Wno-narrowing -Wwrite-strings -Wcast-qual -Wmissing-format-attribute -Woverloaded-virtual -pedantic -Wno-long-long -Wno-variadic-macros -Wno-overlength-strings   -DHAVE_CONFIG_H -static-libstdc++ -static-libgcc  -o cc1 c/c-lang.o c-family/stub-objc.o attribs.o c/c-errors.o c/c-decl.o c/c-typeck.o c/c-convert.o c/c-aux-info.o c/c-objc-common.o c/c-parser.o c/c-array-notation.o c/c-fold.o c-family/c-common.o c-family/c-cppbuiltin.o c-family/c-dump.o c-family/c-format.o c-family/c-gimplify.o c-family/c-indentation.o c-family/c-lex.o c-family/c-omp.o c-family/c-opts.o c-family/c-pch.o c-family/c-ppoutput.o c-family/c-pragma.o c-family/c-pretty-print.o c-family/c-semantics.o c-family/c-ada-spec.o c-family/c-cilkplus.o c-family/array-notation-common.o c-family/cilk.o c-family/c-ubsan.o default-c.o \
  cc1-checksum.o libbackend.a main.o libcommon-target.a libcommon.a ../libcpp/libcpp.a ../libdecnumber/libdecnumber.a libcommon.a ../libcpp/libcpp.a   ../libbacktrace/.libs/libbacktrace.a ../libiberty/libiberty.a ../libdecnumber/libdecnumber.a   -lmpc -lmpfr -lgmp -rdynamic -ldl  -L./../zlib -lz
bfin.o:(.data.rel+0x778): undefined reference to `gnu_libc_printf_pointer_format(tree_node*, char const**)'

(that symbol would be defined in linux.o, but that file is not built for
uclinux).


Segher
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.
	* tree-pass.h (make_pass_sprintf_length): Declare new function.
	* print-tree.c: Increase buffer size.

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/builtin-stringop-chk-1.c: Adjust.
	* 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.
	* gcc.dg/tree-ssa/builtin-sprintf-2.c: New test.

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 332c85e..69ff9fa 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.opt b/gcc/c-family/c.opt
index a5358ed..e0784c8 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 write 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/doc/invoke.texi b/gcc/doc/invoke.texi
index 20be9b7..09cf3a6 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=@var{n} @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
@@ -3878,6 +3879,88 @@  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
+Warn about calls to formatted input/output functions such as @code{sprintf}
+that might overflow the destination buffer, or about bounded functions such
+as @code{snprintf} that might result in output truncation.  When the exact
+number of bytes written by a format directive cannot be determined at
+compile-time it is estimated based on heuristics that depend on the
+@var{level} argument and on optimization.  While enabling optimization
+will in most cases improve the accuracy of the warning, it may also
+result in false positives.
+
+@table @gcctabopt
+@item -Wformat-length
+@item -Wformat-length=1
+@opindex Wformat-length
+@opindex Wno-format-length
+Level @var{1} of @option{-Wformat-length} enabled by @option{-Wformat}
+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 with unknown values are
+assumed to have the value of one, and strings of unknown length to be
+empty.  Numeric arguments that are known to be bounded to a subrange
+of their type, or string arguments whose output is bounded either by
+their directive's precision or by a finite set of string literals, are
+assumed to take on the value within the range that results in the most
+bytes on output.  For example, 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, though it may not be sufficient to avoid the overflow.
+
+@smallexample
+void f (int a, int b)
+@{
+  char buf [12];
+  sprintf (buf, "a = %i, b = %i\n", a, b);
+@}
+@end smallexample
+
+@item -Wformat-length=2
+Level @var{2} warns also about calls that might overflow the destination
+buffer or result in truncation given an argument of sufficient length
+or magnitude.  At level @var{2}, 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 whose length cannot be assumed to be bounded either by
+the directive's precision, or by a finite set of string literals they
+may evaluate to, or the character array they may point to, are assumed
+to be 1 character long.
+
+At level @var{2}, the call in the example above 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 informational note following the warning.
+
+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 format
+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 specifier will reduce
+the required buffer size.  For example, if @var{a} and @var{b} in the
+example 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
+@end table
+
 @item -Wno-format-zero-length
 @opindex Wno-format-zero-length
 @opindex Wformat-zero-length
@@ -7825,6 +7908,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 value of formatted output functions
+such as @code{sprintf}, @code{snprintf}, @code{vsprintf}, and @code{vsnprintf}
+(but not @code{printf} of @code{fprintf}).  This transformation allows GCC
+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 5866260..068d6c8 100644
--- a/gcc/doc/tm.texi
+++ b/gcc/doc/tm.texi
@@ -5349,6 +5349,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 da133a4..4607495 100644
--- a/gcc/doc/tm.texi.in
+++ b/gcc/doc/tm.texi.in
@@ -4081,6 +4081,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..78ef824 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,60 @@  get_maxval_strlen (tree arg, tree *length, bitmap *visited, int type)
 	}
       else
 	val = c_strlen (arg, 1);
+
+      if (!val && 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;
+	    }
+	}
+
       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 +1287,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,8 +1317,13 @@  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))
-              return false;
+	    if (!get_range_strlen (arg, length, visited, type, fuzzy))
+	      {
+		if (fuzzy)
+		  *maxlen = build_all_ones_cst (size_type_node);
+		else
+		  return false;
+	      }
           }
         }
         return true;
@@ -1285,17 +1333,40 @@  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
+   length.  For example, given an expression like 'x ? array : "xyz"'
+   and array 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..5c21415
--- /dev/null
+++ b/gcc/gimple-ssa-sprintf.c
@@ -0,0 +1,2690 @@ 
+/* 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 bound-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 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 fornmat 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 formatted function.  */
+
+struct format_result
+{
+  /* Number of characters written by the formatted function, exact,
+     minimum and maximum when an exact number cannot be determined.
+     Setting the minimum to HOST_WIDE_INT_MAX disables all length
+     tracking for the remainder of the format string.
+     Setting either of the other two members to HOST_WIDE_INT_MAX
+     disables the exact or maximum length tracking, respectively,
+     but continues to track the maximum.  */
+  unsigned HOST_WIDE_INT number_chars;
+  unsigned HOST_WIDE_INT number_chars_min;
+  unsigned HOST_WIDE_INT number_chars_max;
+
+  /* True when the range given by NUMBER_CHARS_MIN and NUMBER_CHARS_MAX
+     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 formatted call is constant (and
+     thus a candidate for string constant folding).  This is rare
+     and typically requires that the arguments of all directives
+     are also constant.  Constant implies bounded.  */
+  bool constant;
+
+  /* True if no individual directive resulted in more than 4095 bytes
+     of output (the total NUMBER_CHARS might be greater).  */
+  bool under4k;
+
+  /* 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 value of INT_MIN for the target.  */
+
+static HOST_WIDE_INT
+target_int_min ()
+{
+  static const unsigned HOST_WIDE_INT int_min
+    = 1LLU << (sizeof int_min * CHAR_BIT
+	       - TYPE_PRECISION (integer_type_node) + 1);
+  return int_min;
+}
+
+/* Return the value of INT_MAX for the target.  */
+
+static unsigned HOST_WIDE_INT
+target_int_max ()
+{
+  static const unsigned HOST_WIDE_INT int_max
+    = HOST_WIDE_INT_M1U >> (sizeof int_max * CHAR_BIT
+			    - TYPE_PRECISION (integer_type_node) + 1);
+  return int_max;
+}
+
+/* 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;
+}
+
+/* The format_warning_at_substring function is not used here in a way
+   that makes using attribute format viable.  Suppress the warning.  */
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wsuggest-attribute=format"
+
+/* For convenience and brevity.  */
+
+static bool
+  (* const fmtwarn) (const substring_loc &, const source_range *,
+		     const char *, int, const char *, ...)
+  = format_warning_at_substring;
+
+#pragma GCC diagnostic pop
+
+/* 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 = range.max = navail;
+      return range;
+    }
+
+  if (res.number_chars < navail)
+    {
+      range.min = range.max = navail - res.number_chars;
+    }
+  else if (res.number_chars_min < navail)
+    {
+      range.max = navail - res.number_chars_min;
+    }
+  else
+    range.max = 0;
+
+  if (res.number_chars_max < navail)
+    range.min = navail - res.number_chars_max;
+  else
+    range.min = 0;
+
+  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 formatted 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 = HOST_WIDE_INT_M1U;
+      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 = HOST_WIDE_INT_M1U;
+      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 greater 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 Value Range Propagation.  */
+	  res.bounded = true;
+	}
+      else if (range_type == VR_ANTI_RANGE)
+	{
+	  /* Handle anti-ranges if/when bug 71690 is 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 is determined to be
+     (e.g., when it's within some range) or when the minimum and maximum
+     are the same.  That can happen here for example when the specified
+     width is as wide as the greater of MIN and MAX, as would be the case
+     with sprintf (d, "%08x", x) with a 32-bit integer x.  */
+  res.bounded |= res.range.min == res.range.max;
+
+  if (res.range.max < res.range.min)
+    {
+      unsigned HOST_WIDE_INT tmp = res.range.max;
+      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 = HOST_WIDE_INT_M1U;
+	  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 = HOST_WIDE_INT_M1U;
+	  return res;
+	}
+    }
+  else if (res.constant && TOUPPER (spec.specifier) != 'A')
+    {
+      /* Specify the precision explicitly since mpfr_sprintf defaults
+	 to zero.  */
+      prec = 6;
+    }
+
+  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;
+    }
+
+  /* Determine the length of the shortest and longest string referenced
+     by STR.  Strings of unknown lengths are bounded by the sizes of
+     arrays that subexpressions of STR may refer to.  Pointers that
+     aren't known to point any such arrays result in LENRANGE[1] set
+     to SIZE_MAX.  */
+  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_M1U);
+
+      /* Set RES.BOUNDED to true if and only if all strings referenced
+	 by STR are known to be bounded (though not necessarily by their
+	 actual length but perhaps by their maximum possible length).  */
+      res.bounded = res.range.max < HOST_WIDE_INT_MAX;
+
+      /* Set RES.CONSTANT to false even though that may be overly
+	 conservative in rare cases like: 'x ? a : b' where a and
+	 b have the same lengths and consist of the same characters.  */
+      res.constant = false;
+      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 equal.  */
+  if (res.range.min == res.range.max && res.range.min < HOST_WIDE_INT_MAX)
+    res.bounded = true;
+
+  return res;
+}
+
+/* Compute the length of the output resulting from the conversion
+   specification SPEC with the argument ARG in a call described by INFO
+   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, TREE_TYPE (info.format),
+			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 = HOST_WIDE_INT_M1U;
+      res->number_chars = HOST_WIDE_INT_M1U;
+    }
+
+  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 = HOST_WIDE_INT_M1U;
+      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)
+	    {
+	      /* The minimum directive output is longer than there is
+		 room in the destination.  */
+	      if (fmtres.range.min == fmtres.range.max)
+		{
+		  const char* fmtstr
+		    = (info.bounded
+		       ? G_("%<%.*s%> directive output truncated writing "
+			    "%wu bytes into a region of size %wu")
+		       : G_("%<%.*s%> directive writing %wu bytes "
+			    "into a region of size %wu"));
+		  warned = fmtwarn (dirloc, pargrange, NULL,
+				    OPT_Wformat_length_, fmtstr,
+				    (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))
+	    {
+	      /* The maximum directive output is longer than there is
+		 room in the destination and the output is either bounded
+		 or the warning level is greater than 1.  */
+	      if (fmtres.range.max >= HOST_WIDE_INT_MAX)
+		{
+		  const char* fmtstr
+		    = (info.bounded
+		       ? G_("%<%.*s%> directive output may be truncated "
+			    "writing %wu or more bytes a region of size %wu")
+		       : G_("%<%.*s%> directive writing %wu or more bytes "
+			    "into a region of size %wu"));
+		  warned = fmtwarn (dirloc, pargrange, NULL,
+				    OPT_Wformat_length_, fmtstr,
+				    (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 = HOST_WIDE_INT_M1U;
+      if (res->number_chars_max < HOST_WIDE_INT_MAX
+	  && fmtres.range.max < HOST_WIDE_INT_MAX)
+	res->number_chars_max += fmtres.range.max;
+
+      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;
+    }
+
+  /* Has the minimum directive output length exceeded the maximum
+     of 4095 bytes required to be supported?  */
+  bool minunder4k = fmtres.range.min < 4096;
+  if (!minunder4k || fmtres.range.max > 4095)
+    res->under4k = false;
+
+  if (!res->warned && 1 < warn_format_length
+      && (!minunder4k || fmtres.range.max > 4095))
+    {
+      /* The directive output may be longer than the maximum required
+	 to be handled by an implementation according to 7.21.6.1, p15
+	 of C11.  Warn on this only at level 2 but remember this and
+	 prevent folding the return value when done.  This allows for
+	 the possibility of the actual libc call failing due to ENOMEM
+	 (like Glibc does under some conditions).  */
+
+      if (fmtres.range.min == fmtres.range.max)
+	res->warned = fmtwarn (dirloc, pargrange, NULL,
+			       OPT_Wformat_length_,
+			       "%<%.*s%> directive output of %wu bytes exceeds "
+			       "minimum required size of 4095",
+			       (int)cvtlen, cvtbeg, fmtres.range.min);
+      else
+	{
+	  const char *fmtstr
+	    = (minunder4k
+	       ? G_("%<%.*s%> directive output between %qu and %wu "
+		    "bytes may exceed minimum required size of 4095")
+	       : G_("%<%.*s%> directive output between %qu and %wu "
+		    "bytes exceeds minimum required size of 4095"));
+
+	  res->warned = fmtwarn (dirloc, pargrange, NULL,
+				 OPT_Wformat_length_, fmtstr,
+				 (int)cvtlen, cvtbeg,
+				 fmtres.range.min, fmtres.range.max);
+	}
+    }
+
+  /* Has the minimum directive output length exceeded INT_MAX?  */
+  bool exceedmin = res->number_chars_min > target_int_max ();
+
+  if (!res->warned
+      && (exceedmin
+	  || (1 < warn_format_length
+	      && res->number_chars_max > target_int_max ())))
+    {
+      /* The directive output causes the total length of output
+	 to exceed INT_MAX bytes.  */
+
+      if (fmtres.range.min == fmtres.range.max)
+	res->warned = fmtwarn (dirloc, pargrange, NULL,
+			       OPT_Wformat_length_,
+			       "%<%.*s%> directive output of %wu bytes causes "
+			       "result to exceed %<INT_MAX%>",
+			       (int)cvtlen, cvtbeg, fmtres.range.min);
+      else
+	{
+	  const char *fmtstr
+	    = (exceedmin
+	       ? G_ ("%<%.*s%> directive output between %wu and %wu "
+		     "bytes causes result to exceed %<INT_MAX%>")
+	       : G_ ("%<%.*s%> directive output between %wu and %wu "
+		     "bytes may cause result to exceed %<INT_MAX%>"));
+	  res->warned = fmtwarn (dirloc, pargrange, NULL,
+				 OPT_Wformat_length_, fmtstr,
+				 (int)cvtlen, cvtbeg,
+				 fmtres.range.min, fmtres.range.max);
+	}
+    }
+}
+
+/* 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, TREE_TYPE (info.format), 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);
+
+      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")));
+
+	  res->warned = fmtwarn (loc, NULL, NULL, OPT_Wformat_length_, text);
+	}
+      else
+	{
+	  /* There isn't enough room for 1 or more characters that remain
+	     to copy from the format string.  */
+	  const char *text
+	    = (info.bounded   // Snprintf and the like.
+	       ? (boundrange
+		  ? G_("output may be truncated at or before format character "
+		       "%qc at offset %wu")
+		  : G_("output truncated at format character %qc at offset %wu"))
+	       : (res->number_chars >= HOST_WIDE_INT_MAX
+		  ? G_("may write format character %#qc at offset %wu past "
+		       "the end of the destination")
+		  : G_("writing format character %#qc at offset %wu past "
+		       "the end of the destination")));
+
+	  res->warned = fmtwarn (loc, NULL, NULL, OPT_Wformat_length_,
+				 text, info.fmtstr[off], off);
+	}
+    }
+
+  if (res->warned && !end && info.objsize < HOST_WIDE_INT_MAX)
+    {
+      /* If a warning has been issued for buffer overflow or truncation
+	 (but not otherwise) 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);
+    }
+
+  /* Add the number of bytes and then check for INT_MAX overflow.  */
+  *res += nbytes;
+
+  /* Has the minimum output length minus the terminating nul exceeded
+     INT_MAX?  */
+  bool exceedmin = (res->number_chars_min - !end) > target_int_max ();
+
+  if (!res->warned
+      && (exceedmin
+	  || (1 < warn_format_length
+	      && (res->number_chars_max - !end) > target_int_max ())))
+    {
+      /* The function's output exceeds INT_MAX bytes.  */
+
+      /* Set NAVAIL to the number of available bytes used to decide
+	 whether or not to issue a warning below.  The exact kind of
+	 warning will depend on AVAIL_RANGE.  */
+      unsigned HOST_WIDE_INT navail = avail_range.max;
+      if (nbytes <= navail && avail_range.min < HOST_WIDE_INT_MAX
+	  && (res->bounded || 1 < warn_format_length))
+	navail = avail_range.min;
+
+      /* Compute the offset of the first format character that is beyond
+	 the end of the destination region and the length of the rest of
+	 the format string from that point on.  */
+      unsigned HOST_WIDE_INT off = (unsigned HOST_WIDE_INT)(beg - info.fmtstr);
+      if (navail < HOST_WIDE_INT_MAX)
+	off += navail;
+
+      size_t len = strlen (info.fmtstr + off);
+
+      substring_loc loc
+	(info.fmtloc, TREE_TYPE (info.format), off - !len, len ? off : 0,
+	 off + len - !!len);
+
+      if (res->number_chars_min == res->number_chars_max)
+	res->warned = fmtwarn (loc, NULL, NULL,
+			       OPT_Wformat_length_,
+			       "output of %wu bytes causes "
+			       "result to exceed %<INT_MAX%>",
+			       res->number_chars_min - !end);
+      else
+	{
+	  const char *text
+	    = (exceedmin
+	       ? G_ ("output between %wu and %wu bytes causes "
+		     "result to exceed %<INT_MAX%>")
+	       : G_ ("output between %wu and %wu bytes may cause "
+		     "result to exceed %<INT_MAX%>"));
+	  res->warned = fmtwarn (loc, NULL, NULL, OPT_Wformat_length_,
+				 text,
+				 res->number_chars_min - !end,
+				 res->number_chars_max - !end);
+	}
+    }
+}
+
+/* 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
+     (with no conversion producing more than 4K worth of output) until
+     determined otherwise.  */
+  res->bounded = true;
+  res->constant = true;
+  res->under4k = true;
+  res->floating = false;
+  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.  */
+
+static unsigned HOST_WIDE_INT
+get_destination_size (tree dest)
+{
+  /* Use __builtin_object_size to determine the size of the destination
+     object.  When optimizing, determine the smallest object (such as
+     a member array as opposed to the whole enclosing object), otherwise
+     use type-zero object size to determine the size of the enclosing
+     object (the function fails without optimization in this type).  */
+  int ost = 0 < optimize;
+  unsigned HOST_WIDE_INT size;
+  if (compute_builtin_object_size (dest, ost, &size))
+    return size;
+
+  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.  A result that isn't suitable for substitution may
+   have its range set to the range of return values, if that is known.  */
+
+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);
+
+  /* Avoid the return value optimization when the behavior of the call
+     is undefined either because any directive may have produced 4K or
+     more of output, or the return value exceeds INT_MAX, or because
+     the ourput overflows the destination object (but leave it enabled
+     when the function is bounded because then the behavior is well-
+     defined).  */
+  if (lhs && res.bounded && res.under4k
+      && (info.bounded || res.number_chars <= info.objsize)
+      && res.number_chars - 1 <= target_int_max ())
+    {
+      /* Replace the left-hand side of the call with the constant
+	 result of the formatted 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
+    {
+      unsigned HOST_WIDE_INT maxbytes;
+
+      if (lhs
+	  && ((maxbytes = res.number_chars - 1) <= target_int_max ()
+	      || (res.number_chars_min - 1 <= target_int_max ()
+		  && (maxbytes = res.number_chars_max - 1) <= target_int_max ()))
+	  && (info.bounded || maxbytes < info.objsize))
+	{
+	  /* If the result is in a valid range bounded by the size of
+	     the destination set it so that it can be used for subsequent
+	     optimizations.  */
+	  int prec = TYPE_PRECISION (integer_type_node);
+
+	  if (res.number_chars < target_int_max () && res.under4k)
+	    {
+	      wide_int num = wi::shwi (res.number_chars - 1, prec);
+	      set_range_info (lhs, VR_RANGE, num, num);
+	    }
+	  else if (res.number_chars_min < target_int_max ()
+		   && res.number_chars_max < target_int_max ())
+	    {
+	      wide_int min = wi::shwi (res.under4k ? res.number_chars_min - 1
+				       : target_int_min (), prec);
+	      wide_int max = wi::shwi (res.number_chars_max - 1, prec);
+	      set_range_info (lhs, VR_RANGE, min, max);
+	    }
+	}
+
+      if (dump_file)
+	{
+	  const char *inbounds
+	    = (res.number_chars_min <= info.objsize
+	       ? (res.number_chars_max <= info.objsize
+		  ? "in" : "potentially out-of")
+	       : "out-of");
+
+	  location_t callloc = gimple_location (info.callstmt);
+	  fprintf (dump_file, "On line %i ", LOCATION_LINE (callloc));
+	  print_generic_expr (dump_file, info.func, dump_flags);
+
+	  const char *ign = lhs ? "" : " ignored";
+	  if (res.number_chars >= HOST_WIDE_INT_MAX)
+	    fprintf (dump_file,
+		     " %s-bounds return value in range [%lu, %lu]%s.\n",
+		     inbounds,
+		     (unsigned long)res.number_chars_min,
+		     (unsigned long)res.number_chars_max, ign);
+	  else
+	    fprintf (dump_file, " %s-bounds return value %lu%s.\n",
+		     inbounds, (unsigned long)res.number_chars, ign);
+	}
+    }
+}
+
+/* Determine if a GIMPLE CALL is 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 byte less than that.  */
+	  if (dstsize >= HOST_WIDE_INT_MAX)
+	    warning_at (gimple_location (info.callstmt), OPT_Wformat_length_,
+			"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), OPT_Wformat_length_,
+		      "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 formatted function,
+     including the terminating NUL.  */
+  format_result res = format_result ();
+  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);
+}
+
+/* Execute the pass for function FUN.  */
+
+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.  */
+
+/* Return a pointer to a pass object newly constructed from the context
+   CTXT.  */
+
+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 533157d..856158b 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.  */
@@ -304,6 +305,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 e55b6bd..60957f9 100644
--- a/gcc/print-tree.c
+++ b/gcc/print-tree.c
@@ -769,7 +769,8 @@  print_node (FILE *file, const char *prefix, tree node, int indent)
 
 	case VECTOR_CST:
 	  {
-	    char buf[10];
+	    /* Big enough for 2 UINT_MAX plus the string below.  */
+	    char buf[32];
 	    unsigned i;
 
 	    for (i = 0; i < VECTOR_CST_NELTS (node); ++i)
diff --git a/gcc/targhooks.c b/gcc/targhooks.c
index 97856fa..17ff1b5 100644
--- a/gcc/targhooks.c
+++ b/gcc/targhooks.c
@@ -1509,6 +1509,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 a2fa49f..1cf1399 100644
--- a/gcc/targhooks.h
+++ b/gcc/targhooks.h
@@ -191,6 +191,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/builtin-stringop-chk-1.c b/gcc/testsuite/gcc.dg/builtin-stringop-chk-1.c
index 6e71aee..e491ff5 100644
--- a/gcc/testsuite/gcc.dg/builtin-stringop-chk-1.c
+++ b/gcc/testsuite/gcc.dg/builtin-stringop-chk-1.c
@@ -1,7 +1,7 @@ 
 /* Test whether buffer overflow warnings for __*_chk builtins
    are emitted properly.  */
 /* { dg-do compile } */
-/* { dg-options "-O2 -std=gnu99 -ftrack-macro-expansion=0" } */
+/* { dg-options "-O2 -Wno-format -std=gnu99 -ftrack-macro-expansion=0" } */
 /* { dg-additional-options "-mstructure-size-boundary=8" { target arm*-*-* } } */
 // { dg-skip-if "packed attribute missing for t" { "epiphany-*-*" } { "*" } { "" } }
 
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-2.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-2.c
new file mode 100644
index 0000000..f7abfd8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-2.c
@@ -0,0 +1,218 @@ 
+/* Test to verify that the return value of calls to __builtin_sprintf
+   is not folded if the call has undefined behavior even if it would
+   otherwise produce a known number of bytes on output, and that if
+   the return value is in a known range the range is not made
+   available to subsequent passes and doesn't affect branching and
+   the removal of code.
+   The test is compiled with warnings disabled to make sure the absence
+   of optimizations does not depend on the presence of warnings.  */
+/* { dg-do compile } */
+/* { dg-options "-O2 -fprintf-return-value -fdump-tree-optimized -ftrack-macro-expansion=0 -w" } */
+
+#ifndef LINE
+# define LINE 0
+#endif
+
+#define INT_MAX __INT_MAX__
+
+char *buf;
+char buf8k [8192];
+
+#define concat(a, b)   a ## b
+#define CAT(a, b)      concat (a, b)
+
+#define EQL(expect, size, fmt, ...)					\
+  void CAT (test_on_line_, __LINE__)(void)				\
+  {									\
+    if (!LINE || LINE == __LINE__)					\
+      {									\
+	char *dst = size < 0 ? buf : buf8k + sizeof buf8k - size;	\
+	int result = __builtin_sprintf (dst, fmt, __VA_ARGS__);		\
+	if (result != expect)						\
+	  __builtin_abort ();						\
+      }									\
+  }
+
+/* Verify that the return value or range or return values from the call
+   to the formatted function is not treated as a constant or made available
+   to subsequent optimization passes.  */
+#define RNG(min, max, size, fmt, ...)					\
+  void CAT (test_on_line_, __LINE__)(void)				\
+  {									\
+    if (!LINE || LINE == __LINE__)					\
+      {									\
+	char *dst = size < 0 ? buf : buf8k + sizeof buf8k - size;	\
+	int result = __builtin_sprintf (dst, fmt, __VA_ARGS__);		\
+	if (result < min || max < result)				\
+	  __builtin_abort ();						\
+      }									\
+  }
+
+extern int i;
+extern long li;
+extern char *str;
+
+/* Verify that overflowing the destination object disables the return
+   value optimization.  */
+EQL (0, 0, "%c",  ' ');
+EQL (0, 0, "%c",  i)
+EQL (0, 0, "%-s", "");
+
+EQL (1, 1, "%c",  'x');
+EQL (1, 1, "%-s", "x");
+
+EQL (4, 4, "%4c", 'x');
+
+/* Verify that exceeding the environmental limit of 4095 bytes for
+   a single conversion specification disables the return value
+   folding.  */
+EQL (   4096, sizeof buf8k, "%4096c", 'x');
+
+EQL (INT_MAX, -1, "%*c", INT_MAX, 'x');
+
+EQL (   4096, sizeof buf8k, "%4096.4094f", 1.0);
+EQL (   4096, sizeof buf8k, "%.4094f",     1.0);
+EQL (   4097, sizeof buf8k, "%.4095f",     1.0);
+
+enum { imax2 = (INT_MAX / 2) * 2 };
+EQL (imax2, -1, "%*c%*c", INT_MAX / 2, 'x', INT_MAX / 2, 'y');
+
+/* Verify that range inforation for calls that overflow the destination
+   isn't available.  */
+RNG (0,  0,  0, "%hhi", i)
+RNG (0,  0,  1, "%hhi", i)
+RNG (0,  1,  1, "%hhi", i)
+RNG (0,  0,  2, "%hhi", i)
+RNG (0,  1,  2, "%hhi", i)
+RNG (0,  2,  2, "%hhi", i)
+RNG (0,  0,  3, "%hhi", i)
+RNG (0,  1,  3, "%hhi", i)
+RNG (0,  2,  3, "%hhi", i)
+RNG (0,  3,  3, "%hhi", i)
+RNG (0,  0,  4, "%hhi", i)
+RNG (0,  1,  4, "%hhi", i)
+RNG (0,  2,  4, "%hhi", i)
+RNG (0,  3,  4, "%hhi", i)
+RNG (0,  4,  4, "%hhi", i)
+
+RNG (0,  0,  0, "%hhu", i)
+RNG (0,  0,  1, "%hhu", i)
+RNG (0,  1,  1, "%hhu", i)
+RNG (0,  0,  2, "%hhu", i)
+RNG (0,  1,  2, "%hhu", i)
+RNG (0,  2,  2, "%hhu", i)
+RNG (0,  0,  3, "%hhu", i)
+RNG (0,  1,  3, "%hhu", i)
+RNG (0,  2,  3, "%hhu", i)
+RNG (0,  3,  3, "%hhu", i)
+
+RNG (0,  0,  0, "%i", i)
+
+RNG (0,  0,  1, "%i", i)
+RNG (0,  1,  1, "%i", i)
+
+RNG (0,  0,  2, "%i", i)
+RNG (0,  1,  2, "%i", i)
+RNG (0,  2,  2, "%i", i)
+
+RNG (0,  0,  3, "%i", i)
+RNG (0,  1,  3, "%i", i)
+RNG (0,  2,  3, "%i", i)
+RNG (0,  3,  3, "%i", i)
+
+RNG (0,  0,  4, "%i", i)
+RNG (0,  1,  4, "%i", i)
+RNG (0,  2,  4, "%i", i)
+RNG (0,  3,  4, "%i", i)
+RNG (0,  4,  4, "%i", i)
+
+RNG (0,  0,  5, "%i", i)
+RNG (0,  1,  5, "%i", i)
+RNG (0,  2,  5, "%i", i)
+RNG (0,  3,  5, "%i", i)
+RNG (0,  4,  5, "%i", i)
+RNG (0,  5,  5, "%i", i)
+
+RNG (0,  0,  6, "%i", i)
+RNG (0,  1,  6, "%i", i)
+RNG (0,  2,  6, "%i", i)
+RNG (0,  3,  6, "%i", i)
+RNG (0,  4,  6, "%i", i)
+RNG (0,  5,  6, "%i", i)
+RNG (0,  6,  6, "%i", i)
+
+RNG (0,  0,  7, "%i", i)
+RNG (0,  1,  7, "%i", i)
+RNG (0,  2,  7, "%i", i)
+RNG (0,  3,  7, "%i", i)
+RNG (0,  4,  7, "%i", i)
+RNG (0,  5,  7, "%i", i)
+RNG (0,  6,  7, "%i", i)
+
+#if __SIZEOF_INT__ == 4
+
+/* A 32-bit int takes up at most 11 bytes (-2147483648) not including
+   the terminating nul.  */
+RNG (0,  7,  7, "%i", i)
+
+RNG (0,  0,  8, "%i", i)
+RNG (0,  1,  8, "%i", i)
+RNG (0,  2,  8, "%i", i)
+RNG (0,  3,  8, "%i", i)
+RNG (0,  4,  8, "%i", i)
+RNG (0,  5,  8, "%i", i)
+RNG (0,  6,  8, "%i", i)
+RNG (0,  7,  8, "%i", i)
+RNG (0,  8,  8, "%i", i)
+
+RNG (0,  0,  9, "%i", i)
+RNG (0,  1,  9, "%i", i)
+RNG (0,  2,  9, "%i", i)
+RNG (0,  3,  9, "%i", i)
+RNG (0,  4,  9, "%i", i)
+RNG (0,  5,  9, "%i", i)
+RNG (0,  6,  9, "%i", i)
+RNG (0,  7,  9, "%i", i)
+RNG (0,  8,  9, "%i", i)
+RNG (0,  9,  9, "%i", i)
+
+RNG (0,  0, 10, "%i", i)
+RNG (0,  1, 10, "%i", i)
+RNG (0,  2, 10, "%i", i)
+RNG (0,  3, 10, "%i", i)
+RNG (0,  4, 10, "%i", i)
+RNG (0,  5, 10, "%i", i)
+RNG (0,  6, 10, "%i", i)
+RNG (0,  7, 10, "%i", i)
+RNG (0,  8, 10, "%i", i)
+RNG (0,  9, 10, "%i", i)
+RNG (0, 10, 10, "%i", i)
+
+#endif
+
+/* Verify the result of a conditional expression involving a string
+   literal and an unknown string isn't optimized.  */
+RNG (0,  1,   4, "%-s", i ? str : "123");
+RNG (0,  1,   4, "%-s", i ? "123" : str);
+
+/* Verify that no call to abort has been eliminated and that each call
+   is at the beginning of a basic block (and thus the result of a branch).
+   This latter test tries to verify that the test preceding the call to
+   abort has not been eliminated either.
+
+   The expected output looks something like this:
+
+   <bb 2>:
+   result_3 = __builtin_sprintf (&MEM[(void *)&buf8k + 8192B], "%c", 32);
+   if (result_3 != 0)
+     goto <bb 3>;
+   else
+     goto <bb 4>;
+
+   <bb 3>:
+   __builtin_abort ();
+
+*/
+
+/* { dg-final { scan-tree-dump-times ">:\n *__builtin_abort" 105 "optimized" { target { ilp32 || lp64 } } } } */
+/* { dg-final { scan-tree-dump-times ">:\n *__builtin_abort" 74 "optimized" { target { { ! ilp32 } && { ! lp64 } } } } } */
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..eb8ab55
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c
@@ -0,0 +1,1417 @@ 
+/* { 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
+
+#define INT_MAX __INT_MAX__
+
+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');
+
+  /* Verify that a warning is issued for exceeding INT_MAX bytes and
+     not otherwise.  */
+  T (-1, "%*c",  INT_MAX - 1, '1');
+  T (-1, "%*c",  INT_MAX,     '1');
+  T (-1, "X%*c", INT_MAX - 1, '1');
+  T (-1, "X%*c", INT_MAX,     '1'); /* { dg-warning "directive output of \[0-9\]+ bytes causes result to exceed .INT_MAX." } */
+
+  T (-1, "%*c%*c", INT_MAX - 1, '1', INT_MAX - 1, '2'); /* { dg-warning "directive output of \[0-9\]+ bytes causes result to exceed .INT_MAX." } */
+
+  T (-1, "%*cX", INT_MAX - 2, '1');
+  T (-1, "%*cX", INT_MAX - 1, '1');
+  T (-1, "%*cX", INT_MAX,     '1'); /* { dg-warning "output of \[0-9\]+ bytes causes result to exceed .INT_MAX." } */
+}
+
+/* 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 } 94 } */
+
+  /* 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 } 106 } */
+  /* { dg-warning "nul past the end" "Generic %p" { target *-*-uclinux } 106 } */
+}
+
+/* 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);
+}
+
+/* Same as above but for zero-length arrays.  */
+
+void test_sprintf_zero_length_array (void *p, int i)
+{
+  struct S
+  {
+    int n;
+    char a [0];
+  } *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",     "");        /* { 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" } */
+
+  /* Verify that output in excess of INT_MAX bytes is diagnosed even
+     when the size of the destination object is unknown.  */
+  T (-1, "%*s",  INT_MAX - 1, "");
+  T (-1, "%*s",  INT_MAX,     "");
+  T (-1, "X%*s", INT_MAX,     ""); /* { dg-warning "directive output of \[0-9\]+ bytes causes result to exceed .INT_MAX." } */
+ 
+  /* 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 \[0-9\]+ 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..b0f2d45
--- /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..625d055
--- /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..1e50be1
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf.c
@@ -0,0 +1,540 @@ 
+/* 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 enabled 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 -Wall -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, int min, int max,
+	char *dst, const char *fmt, __builtin_va_list va)
+{
+  int n = __builtin_vsprintf (dst, fmt, va);
+  int len = __builtin_strlen (dst);
+
+  ++ntests;
+
+  int fail = 0;
+  if (n != res)
+    {
+      __builtin_printf ("FAIL: %s:%i: \"%s\" expected result for \"%s\" "
+			"doesn't match function call return value: ",
+			func, line, fmt, dst);
+      if (min == max)
+	__builtin_printf ("%i != %i\n", n, min);
+      else
+	__builtin_printf ("%i not in [%i, %i]\n", n, min, max);
+
+      fail = 1;
+    }
+  else
+    {
+      if (len < min || max < len)
+	{
+	  __builtin_printf ("FAIL: %s:%i: \"%s\" expected result for \"%s\" "
+			    "doesn't match output length: ",
+			    func, line, fmt, dst);
+
+	  if (min == max)
+	    __builtin_printf ("%i != %i\n", len, min);
+	  else
+	    __builtin_printf ("%i not in [%i, %i]\n", len, min, max);
+
+	  fail = 1;
+	}
+      else
+	__builtin_printf ("PASS: %s:%i: \"%s\" result %i: \"%s\"\n",
+			  func, line, fmt, n, dst);
+    }
+
+  if (fail)
+    ++nfails;
+}
+
+void __attribute__ ((noclone, noinline))
+check (const char *func, int line, int res, int min, int max,
+       char *dst, const char *fmt, ...)
+{
+  __builtin_va_list va;
+  __builtin_va_start (va, fmt);
+  checkv (func, line, res, min, max, dst, fmt, va);
+  __builtin_va_end (va);
+}
+
+char buffer[256];
+char* volatile dst = buffer;
+char* ptr = 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, ignore1, ignore2, ignore3)			\
+  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, result, min, max)				\
+  if (min == max)							\
+    __builtin_printf ("FAIL: %s:%i: expected %i, got %i\n",		\
+		      __func__, line, min, result);			\
+  else									\
+    __builtin_printf ("FAIL: %s:%i: expected range [%i, %i], got %i\n",	\
+		      __func__, line, min, max, result);
+#endif
+
+/* Verify that the result is exactly equal to RES.  */
+#define EQL(expect, size, fmt, ...)					\
+  if (!LINE || LINE == __LINE__)					\
+    do {								\
+      char *buf = (size) < 0 ? ptr : buffer + sizeof buffer - (size);	\
+      int result = __builtin_sprintf (buf, fmt, __VA_ARGS__);		\
+      if (result != expect)						\
+	{								\
+	  TEST_FAILURE (__LINE__, expect, expect, result);		\
+	}								\
+      check (__func__, __LINE__, result, expect, expect, dst, fmt,	\
+	     __VA_ARGS__);						\
+    } while (0)
+
+/* Verify that the result is in the range [MIN, MAX].  */
+#define RNG(min, max, size, fmt, ...)					\
+  if (!LINE || LINE == __LINE__)					\
+    do {								\
+      char *buf = (size) < 0 ? ptr : buffer + sizeof buffer - (size);	\
+      int result = __builtin_sprintf (buf, fmt, __VA_ARGS__);		\
+      if (result < min || max < result)					\
+	{								\
+	  TEST_FAILURE (__LINE__, min, max, result);			\
+	}								\
+      check (__func__, __LINE__, result, min, max, dst, fmt,		\
+	     __VA_ARGS__);						\
+    } while (0)
+
+static void __attribute__ ((noinline, noclone))
+test_c (char c)
+{
+  EQL (1,  2, "%c",       c);
+  EQL (1, -1, "%c",       c);
+  EQL (1,  2, "%1c",      c);
+  EQL (1, -1, "%1c",      c);
+  EQL (1,  2, "%*c",      1, c);
+  EQL (1, -1, "%*c",      1, c);
+  EQL (2,  3, "%c%c",     '1', '2');
+  EQL (2, -1, "%c%c",     '1', '2');
+  EQL (3,  4, "%3c",      c);
+  EQL (3, -1, "%3c",      c);
+  EQL (3,  4, "%*c",      3, c);
+  EQL (3, -1, "%*c",      3, c);
+
+  EQL (3,  4, "%*c%*c",     2,  c,  1,  c);
+  EQL (3,  4, "%*c%*c",     1,  c,  2,  c);
+  EQL (3,  4, "%c%c%c",        '1',    '2',    '3');
+  EQL (3,  4, "%*c%c%c",    1, '1',    '2',    '3');
+  EQL (3,  4, "%*c%*c%c",   1, '1', 1, '2',    '3');
+  EQL (3,  4, "%*c%*c%*c",  1, '1', 1, '2', 1, '3');
+
+  EQL (3, -1, "%*c%*c",     2,  c,  1,  c);
+  EQL (3, -1, "%*c%*c",     1,  c,  2,  c);
+  EQL (3, -1, "%c%c%c",        '1',    '2',    '3');
+  EQL (3, -1, "%*c%c%c",    1, '1',    '2',    '3');
+  EQL (3, -1, "%*c%*c%c",   1, '1', 1, '2',    '3');
+  EQL (3, -1, "%*c%*c%*c",  1, '1', 1, '2', 1, '3');
+
+  EQL (4,  5, "%c%c %c",  '1', '2', '3');
+  EQL (5,  6, "%c %c %c", '1', '2', '3');
+  EQL (5,  6, "%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)
+{
+  /*    +-------------------------- expected return value */
+  /*    |   +---------------------- destination size */
+  /*    |   |  +------------------- format string */
+  /*    |   |  |                +-- variable argument(s) */
+  /*    |   |  |                | */
+  /*    V   V  V                V */
+  EQL ( 1,  2, "%d",            0);
+  EQL ( 2,  3, "%d%d",          0,   1);
+  EQL ( 3,  4, "%d%d",          9,  10);
+  EQL ( 4,  5, "%d%d",         11,  12);
+  EQL ( 5,  6, "%d:%d",        12,  34);
+  EQL ( 5,  6, "%d",           12345);
+  EQL ( 6,  7, "%d",          -12345);
+  EQL (15, 16, "%d:%d:%d:%d", 123, 124, 125, 126);
+
+  EQL ( 1,  2, "%i", uchar_range (0, 9));
+  EQL ( 1, -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].
+  EQL ( 2,  3, "%i", uchar_range (10, 99));
+  EQL ( 3,  4, "%i", uchar_range (100, 199));
+  */
+
+  /* Verify that the width allows the return value in the following
+     calls can be folded despite the unknown value of the argument.  */
+#if __SIZEOF_INT__ == 2
+  EQL ( 6,  7, "%6d",      i);
+  EQL ( 6,  7, "%+6d",     i);
+  EQL ( 6,  7, "%-6d",     i);
+  EQL ( 6,  7, "%06d",     i);
+#elif __SIZEOF_INT__ == 4
+  EQL (11, 12, "%11d",     i);
+  EQL (11, 12, "%+11d",    i);
+  EQL (11, 12, "%-11d",    i);
+  EQL (11, 12, "%011d",    i);
+#elif __SIZEOF_INT__ == 8
+  EQL (20, 21, "%20d",     i);
+  EQL (20, 21, "%+20d",    i);
+  EQL (20, 21, "%-29d",    i);
+  EQL (20, 21, "%020d",    i);
+#endif
+
+#if __SIZEOF_LONG__ == 2
+  EQL ( 6,  7, "%6ld",      li);
+  EQL ( 6,  7, "%+6ld",     li);
+  EQL ( 6,  7, "%-6ld",     li);
+  EQL ( 6,  7, "%06ld",     li);
+#elif __SIZEOF_LONG__ == 4
+  EQL (11, 12, "%11ld",     li);
+  EQL (11, 12, "%+11ld",    li);
+  EQL (11, 12, "%-11ld",    li);
+  EQL (11, 12, "%011ld",    li);
+#elif __SIZEOF_LONG__ == 8
+  EQL (20, 21, "%20ld",     li);
+  EQL (20, 21, "%+20ld",    li);
+  EQL (20, 21, "%-20ld",    li);
+  EQL (20, 21, "%020ld",    li);
+#endif
+
+  /* Verify that the output of a directive with an unknown argument
+     is correctly determined at compile time to be in the expected
+     range.  */
+
+  /*    +---------------------------- expected minimum return value */
+  /*    |   +------------------------ expected maximum return value */
+  /*    |   |   +-------------------- destination size */
+  /*    |   |   |  +----------------- format string */
+  /*    |   |   |  |           +----- variable argument(s) */
+  /*    |   |   |  |           | */
+  /*    V   V   V  V           V */
+  RNG ( 1,  4,  5, "%hhi",     i);
+  RNG ( 1,  3,  4, "%hhu",     i);
+
+#if __SIZEOF_SHORT__ == 2
+  RNG ( 1,  6,  7, "%hi",      i);
+  RNG ( 1,  5,  6, "%hu",      i);
+#elif __SIZEOF_SHORT__ == 4
+  RNG ( 1, 11, 12, "%hi",      i);
+  RNG ( 1, 10, 11, "%hu",      i);
+#endif
+
+#if __SIZEOF_INT__ == 2
+  RNG ( 1,  6,  7, "%i",       i);
+  RNG ( 1,  5,  6, "%u",       i);
+#elif __SIZEOF_INT__ == 4
+  RNG ( 1, 11, 12, "%i",       i);
+  RNG ( 1, 10, 11, "%u",       i);
+#elif __SIZEOF_INT__ == 8
+  RNG ( 1, 20, 21, "%i",       i);
+  RNG ( 1, 19, 20, "%u",       i);
+#endif
+
+#if __SIZEOF_LONG__ == 4
+  RNG ( 1, 11, 12, "%li",      li);
+  RNG ( 1, 10, 11, "%lu",      li);
+#elif __SIZEOF_LONG__ == 8
+  RNG ( 1, 20, 21, "%li",      li);
+  RNG ( 1, 19, 20, "%lu",      li);
+#endif
+}
+
+static void __attribute__ ((noinline, noclone))
+test_x (unsigned char uc, unsigned short us, unsigned ui)
+{
+  EQL ( 1,  2, "%hhx",          0);
+  EQL ( 2,  3, "%2hhx",         0);
+  EQL ( 2,  3, "%02hhx",        0);
+  EQL ( 2,  3, "%#02hhx",       0);
+
+  EQL ( 1,  2, "%hhx",          1);
+  EQL ( 2,  3, "%2hhx",         1);
+  EQL ( 2,  3, "%02hhx",        1);
+  EQL ( 3,  4, "%#02hhx",       1);
+
+  EQL ( 2,  3, "%2hhx",        uc);
+  EQL ( 2,  3, "%02hhx",       uc);
+  EQL ( 5,  6, "%#05hhx",      uc);
+
+  EQL ( 2,  3, "%2hhx",        us);
+  EQL ( 2,  3, "%02hhx",       us);
+  EQL ( 5,  6, "%#05hhx",      us);
+
+  EQL ( 2,  3, "%2hhx",        ui);
+  EQL ( 2,  3, "%02hhx",       ui);
+  EQL ( 5,  6, "%#05hhx",      ui);
+
+  EQL ( 1,  2, "%x",            0);
+  EQL ( 1,  2, "%#x",           0);
+  EQL ( 1,  2, "%#0x",          0);
+  EQL ( 1,  2, "%x",            1);
+  EQL ( 1,  2, "%x",          0xf);
+  EQL ( 2,  3, "%x",         0x10);
+  EQL ( 2,  3, "%x",         0xff);
+  EQL ( 3,  4, "%x",        0x100);
+
+  EQL (11, 12, "%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).
+  EQL (11, 12, "%02x:%02x:%02x:%02x",   uc,   uc,   uc,   uc);
+  */
+  EQL (11, 12, "%02hhx:%02hhx:%02hhx:%02hhx",   uc,   uc,   uc,   uc);
+
+#if __SIZEOF_SHORT__ == 2
+  EQL ( 4,  5, "%04hx",                   us);
+  EQL ( 9, 10, "%04hx:%04hx",             us, us);
+  EQL (14, 15, "%04hx:%04hx:%04hx",       us, us, us);
+  EQL (19, 20, "%04hx:%04hx:%04hx:%04hx", us, us, us, us);
+#endif
+
+#if __SIZEOF_INT__ == 2
+  EQL ( 4,  5, "%04x", ui);
+  EQL ( 6,  7, "%#06x", ui);
+#elif __SIZEOF_INT__ == 4
+  EQL ( 8,  9, "%08x", ui);
+  EQL (10, 10 + 1, "%#010x", ui);
+#elif __SIZEOF_INT__ == 8
+  EQL (16, 17, "%016x", ui);
+  EQL (18, 19, "%#018x",  ui);
+#endif
+}
+
+static void __attribute__ ((noinline, noclone))
+test_a_double (void)
+{
+  EQL ( 6,  7, "%a",   0.0);        /* 0x0p+0 */
+  EQL ( 6,  7, "%a",   1.0);        /* 0x8p-3 */
+  EQL ( 6,  7, "%a",   2.0);        /* 0x8p-2 */
+
+  EQL ( 8,  9, "%.1a", 3.0);        /* 0xc.0p-2 */
+  EQL ( 9, 10, "%.2a", 4.0);        /* 0xa.00p-1 */
+}
+
+static void __attribute__ ((noinline, noclone))
+test_a_long_double (void)
+{
+  EQL ( 6,  7, "%La",   0.0L);      /* 0x0p+0 */
+  EQL ( 6,  7, "%La",   1.0L);      /* 0x8p-3 */
+  EQL ( 6,  7, "%La",   2.0L);      /* 0x8p-2 */
+
+  EQL ( 8,  9, "%.1La", 3.0L);      /* 0xc.0p-2 */
+  EQL ( 9, 10, "%.2La", 4.0L);      /* 0xa.00p-1 */
+}
+
+static void __attribute__ ((noinline, noclone))
+test_e_double (void)
+{
+  EQL (12, 13, "%e",  1.0e0);
+  EQL (13, 14, "%e", -1.0e0);
+  EQL (12, 13, "%e",  1.0e+1);
+  EQL (13, 14, "%e", -1.0e+1);
+  EQL (12, 13, "%e",  1.0e+12);
+  EQL (13, 14, "%e", -1.0e+12);
+  EQL (13, 14, "%e",  1.0e+123);
+  EQL (14, 15, "%e", -1.0e+123);
+
+  EQL (12, 13, "%e",  9.999e+99);
+  EQL (12, 13, "%e",  9.9999e+99);
+  EQL (12, 13, "%e",  9.99999e+99);
+
+  /* The actual output of the following directive depends on the rounding
+     mode.  */
+  /* EQL (12, "%e",  9.9999994e+99); */
+
+  EQL (12, 13, "%e",  1.0e-1);
+  EQL (12, 13, "%e",  1.0e-12);
+  EQL (13, 14, "%e",  1.0e-123);
+}
+
+static void __attribute__ ((noinline, noclone))
+test_e_long_double (void)
+{
+  EQL (12, 13, "%Le",  1.0e0L);
+  EQL (13, 14, "%Le", -1.0e0L);
+  EQL (12, 13, "%Le",  1.0e+1L);
+  EQL (13, 14, "%Le", -1.0e+1L);
+  EQL (12, 13, "%Le",  1.0e+12L);
+  EQL (13, 14, "%Le", -1.0e+12L);
+  EQL (13, 14, "%Le",  1.0e+123L);
+  EQL (14, 15, "%Le", -1.0e+123L);
+
+  EQL (12, 13, "%Le",  9.999e+99L);
+  EQL (12, 13, "%Le",  9.9999e+99L);
+  EQL (12, 13, "%Le",  9.99999e+99L);
+  EQL (12, 13, "%Le",  9.999999e+99L);
+
+  /* The actual output of the following directive depends on the rounding
+     mode.  */
+  /* EQL (12, "%Le",  9.9999994e+99L); */
+
+  EQL (12, 13, "%Le",  1.0e-1L);
+  EQL (12, 13, "%Le",  1.0e-12L);
+  EQL (13, 14, "%Le",  1.0e-123L);
+
+  EQL ( 6,  7, "%.0Le",   1.0e-111L);
+  EQL ( 8,  9, "%.1Le",   1.0e-111L);
+  EQL (19, 20, "%.12Le",  1.0e-112L);
+  EQL (20, 21, "%.13Le",  1.0e-113L);
+}
+
+static void __attribute__ ((noinline, noclone))
+test_f_double (void)
+{
+  EQL (  8,   9, "%f", 0.0e0);
+  EQL (  8,   9, "%f", 0.1e0);
+  EQL (  8,   9, "%f", 0.12e0);
+  EQL (  8,   9, "%f", 0.123e0);
+  EQL (  8,   9, "%f", 0.1234e0);
+  EQL (  8,   9, "%f", 0.12345e0);
+  EQL (  8,   9, "%f", 0.123456e0);
+  EQL (  8,   9, "%f", 1.234567e0);
+
+  EQL (  9,  10, "%f", 1.0e+1);
+  EQL ( 20,  21, "%f", 1.0e+12);
+  EQL (130, 131, "%f", 1.0e+123);
+
+  EQL (  8,   9, "%f", 1.0e-1);
+  EQL (  8,   9, "%f", 1.0e-12);
+  EQL (  8,   9, "%f", 1.0e-123);
+}
+
+static void __attribute__ ((noinline, noclone))
+test_f_long_double (void)
+{
+  EQL (  8,   9, "%Lf", 0.0e0L);
+  EQL (  8,   9, "%Lf", 0.1e0L);
+  EQL (  8,   9, "%Lf", 0.12e0L);
+  EQL (  8,   9, "%Lf", 0.123e0L);
+  EQL (  8,   9, "%Lf", 0.1234e0L);
+  EQL (  8,   9, "%Lf", 0.12345e0L);
+  EQL (  8,   9, "%Lf", 0.123456e0L);
+  EQL (  8,   9, "%Lf", 1.234567e0L);
+
+  EQL (  9,  10, "%Lf", 1.0e+1L);
+  EQL ( 20,  21, "%Lf", 1.0e+12L);
+  EQL (130, 131, "%Lf", 1.0e+123L);
+
+  EQL (  8,   9, "%Lf", 1.0e-1L);
+  EQL (  8,   9, "%Lf", 1.0e-12L);
+  EQL (  8,   9, "%Lf", 1.0e-123L);
+}
+
+static void __attribute__ ((noinline, noclone))
+test_s (int i)
+{
+  EQL (  0,   1, "%s", "");
+  EQL (  0,   1, "%s", "\0");
+  EQL (  1,   2, "%1s", "");
+  EQL (  1,   2, "%s", "1");
+  EQL (  2,   3, "%2s", "");
+  EQL (  2,   3, "%s", "12");
+  EQL (  2,   3, "%s%s", "12", "");
+  EQL (  2,   3, "%s%s", "", "12");
+  EQL (  2,   3, "%s%s", "1", "2");
+  EQL (  3,   4, "%3s", "");
+  EQL (  3,   4, "%3s", "1");
+  EQL (  3,   4, "%3s", "12");
+  EQL (  3,   4, "%3s", "123");
+  EQL (  3,   4, "%3.3s", "1");
+  EQL (  3,   4, "%3.3s", "12");
+  EQL (  3,   4, "%3.3s", "123");
+  EQL (  3,   4, "%3.3s", "1234");
+  EQL (  3,   4, "%3.3s", "12345");
+  EQL (  3,   4, "%s %s", "1", "2");
+  EQL (  4,   5, "%s %s", "12", "3");
+  EQL (  5,   6, "%s %s", "12", "34");
+  EQL (  5,   6, "[%s %s]", "1", "2");
+  EQL (  6,   7, "[%s %s]", "12", "3");
+  EQL (  7,   8, "[%s %s]", "12", "34");
+
+  /* Verify the result of a conditional expression involving string
+     literals is in the expected range of their lengths.  */
+  RNG (  0,   3,   4, "%-s", i ? ""    : "123");
+  RNG (  1,   4,   5, "%-s", i ? "1"   : "1234");
+  RNG (  2,   5,   6, "%-s", i ? "12"  : "12345");
+  RNG (  3,   6,   7, "%-s", i ? "123" : "123456");
+}
+
+int main (void)
+{
+  test_c ('?');
+  test_d_i (0xdeadbeef, 0xdeadbeefL);
+  test_x ('?', 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 (0);
+
+  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 c0059de..f3ce6c8 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);