This is the mail archive of the gcc-patches@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)


Thanks for the clarification.  IIUC, there are at least three
possibilities for how to proceed: leave it as is (no checking
with LTO), link LTO with C_COMMON_OBJS, or move the c-format.c
code into the middle end.  Do you have a preference for one of
these?  Or is there another solution that I missed?

Another solution is to implement this somewhen before LTO
bytecode is streamed out thus at the end of early optimizations.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

gcc/c-family/ChangeLog:

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

gcc/cp/ChangeLog:

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

gcc/testsuite/ChangeLog:

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

gcc/ChangeLog:

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

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

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]