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]

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


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

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

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

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

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

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

Thanks
Martin

PS There are a few FIXME notes in the patch that I will either
fix or remove, depending on feedback, before committing the
patch.
PR middle-end/49905 - Better sanity checking on sprintf src & dest
  to produce warning for dodgy code

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

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

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

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

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

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

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

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

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

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

diff --git a/gcc/builtins.c b/gcc/builtins.c
index 5d234a5..657c607 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -175,6 +175,7 @@ static rtx expand_builtin_memory_chk (tree, rtx, machine_mode,
 				      enum built_in_function);
 static void maybe_emit_chk_warning (tree, enum built_in_function);
 static void maybe_emit_sprintf_chk_warning (tree, enum built_in_function);
+static void maybe_emit_snprintf_trunc_warning (tree);
 static void maybe_emit_free_warning (tree);
 static tree fold_builtin_object_size (tree, tree);
 
@@ -6688,11 +6689,17 @@ expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode,
     case BUILT_IN_STPNCPY_CHK:
     case BUILT_IN_STRCAT_CHK:
     case BUILT_IN_STRNCAT_CHK:
+
     case BUILT_IN_SNPRINTF_CHK:
     case BUILT_IN_VSNPRINTF_CHK:
       maybe_emit_chk_warning (exp, fcode);
       break;
 
+    case BUILT_IN_SNPRINTF:
+    case BUILT_IN_VSNPRINTF:
+      maybe_emit_snprintf_trunc_warning (exp);
+      break;
+
     case BUILT_IN_SPRINTF_CHK:
     case BUILT_IN_VSPRINTF_CHK:
       maybe_emit_sprintf_chk_warning (exp, fcode);
@@ -9346,6 +9353,12 @@ maybe_emit_chk_warning (tree exp, enum built_in_function fcode)
       break;
     case BUILT_IN_SNPRINTF_CHK:
     case BUILT_IN_VSNPRINTF_CHK:
+      /* Diagnose output truncation with -Wformat-length.  */
+      maybe_emit_snprintf_trunc_warning (exp);
+      /* -Wformat-length also diagnoses buffer size in excess of
+	 the object size so avoid diagnosing it again below.  */
+      if (warn_format_length)
+	return;
       len = CALL_EXPR_ARG (exp, 1);
       size = CALL_EXPR_ARG (exp, 3);
       break;
@@ -9387,34 +9400,52 @@ maybe_emit_chk_warning (tree exp, enum built_in_function fcode)
 	      exp, get_callee_fndecl (exp));
 }
 
+/* Emit warning if output truncation is detected at compile time
+   in __snprintf/__vsnprintf calls.  */
+
+static void
+maybe_emit_snprintf_trunc_warning (tree exp)
+{
+  /* Verify the required arguments in the original call.  */
+  int nargs = call_expr_nargs (exp);
+
+  if (nargs < 3)
+    return;
+
+  tree fundecl = TREE_OPERAND (CALL_EXPR_FN (exp), 0);
+  tree attrs = TYPE_ATTRIBUTES (TREE_TYPE (fundecl));
+  tree *params = &CALL_EXPR_STATIC_CHAIN (exp) + 1;
+  lang_hooks.decls.check_format_length (fundecl, attrs, nargs, params);
+}
+
 /* Emit warning if a buffer overflow is detected at compile time
    in __sprintf_chk/__vsprintf_chk calls.  */
 
 static void
 maybe_emit_sprintf_chk_warning (tree exp, enum built_in_function fcode)
 {
-  tree size, len, fmt;
-  const char *fmt_str;
   int nargs = call_expr_nargs (exp);
 
   /* Verify the required arguments in the original call.  */
 
   if (nargs < 4)
     return;
-  size = CALL_EXPR_ARG (exp, 2);
-  fmt = CALL_EXPR_ARG (exp, 3);
 
+  tree size = CALL_EXPR_ARG (exp, 2);
   if (! tree_fits_uhwi_p (size) || integer_all_onesp (size))
     return;
 
   /* Check whether the format is a literal string constant.  */
-  fmt_str = c_getstr (fmt);
+  tree fmt = CALL_EXPR_ARG (exp, 3);
+  const char *fmt_str = c_getstr (fmt);
   if (fmt_str == NULL)
     return;
 
   if (!init_target_chars ())
     return;
 
+  tree len = NULL_TREE;
+
   /* If the format doesn't contain % args or %%, we know its size.  */
   if (strchr (fmt_str, target_percent) == 0)
     len = build_int_cstu (size_type_node, strlen (fmt_str));
@@ -9423,25 +9454,32 @@ maybe_emit_sprintf_chk_warning (tree exp, enum built_in_function fcode)
   else if (fcode == BUILT_IN_SPRINTF_CHK
 	   && strcmp (fmt_str, target_percent_s) == 0)
     {
-      tree arg;
-
       if (nargs < 5)
 	return;
-      arg = CALL_EXPR_ARG (exp, 4);
+      tree arg = CALL_EXPR_ARG (exp, 4);
       if (! POINTER_TYPE_P (TREE_TYPE (arg)))
 	return;
 
       len = c_strlen (arg, 1);
-      if (!len || ! tree_fits_uhwi_p (len))
-	return;
     }
-  else
-    return;
 
-  if (! tree_int_cst_lt (len, size))
-    warning_at (tree_nonartificial_location (exp),
-		0, "%Kcall to %D will always overflow destination buffer",
-		exp, get_callee_fndecl (exp));
+  if (!len || !tree_fits_uhwi_p (len))
+    {
+      /* Check the whole format strings and ist arguments for possible
+	 buffer overflow or truncation.  */
+      tree fundecl = TREE_OPERAND (CALL_EXPR_FN (exp), 0);
+      tree attrs = TYPE_ATTRIBUTES (TREE_TYPE (fundecl));
+      tree *params = &CALL_EXPR_STATIC_CHAIN (exp) + 1;
+      lang_hooks.decls.check_format_length (fundecl, attrs, nargs, params);
+      return;
+    }
+  else if (!tree_int_cst_lt (len, size))
+    {
+      len = fold_build2 (PLUS_EXPR, size_type_node, len, integer_one_node);
+      warning_at (tree_nonartificial_location (exp),
+		  0, "%K %D writing %qE bytes into an object of size %qE",
+		  exp, get_callee_fndecl (exp), len, size);
+    }
 }
 
 /* Emit warning if a free is called with address of a variable.  */
diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c
index 85f3a03..524fbc5 100644
--- a/gcc/c-family/c-common.c
+++ b/gcc/c-family/c-common.c
@@ -9718,7 +9718,8 @@ handle_designated_init_attribute (tree *node, tree name, tree, int,
    There are NARGS arguments in the array ARGARRAY.  LOC should be used for
    diagnostics.  */
 void
-check_function_arguments (location_t loc, const_tree fntype, int nargs,
+check_function_arguments (location_t loc, const_tree fndecl,
+			  const_tree fntype, int nargs,
 			  tree *argarray)
 {
   /* Check for null being passed in a pointer argument that must be
@@ -9730,7 +9731,7 @@ check_function_arguments (location_t loc, const_tree fntype, int nargs,
   /* Check for errors in format strings.  */
 
   if (warn_format || warn_suggest_attribute_format)
-    check_function_format (TYPE_ATTRIBUTES (fntype), nargs, argarray);
+    check_function_format (fndecl, TYPE_ATTRIBUTES (fntype), nargs, argarray);
 
   if (warn_format)
     check_function_sentinel (fntype, nargs, argarray);
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index 4e6aa00..9f6012c 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -782,7 +782,8 @@ extern const char *fname_as_string (int);
 extern tree fname_decl (location_t, unsigned, tree);
 
 extern int check_user_alignment (const_tree, bool);
-extern void check_function_arguments (location_t loc, const_tree, int, tree *);
+extern void check_function_arguments (location_t loc, const_tree, const_tree,
+				      int, tree *);
 extern void check_function_arguments_recurse (void (*)
 					      (void *, tree,
 					       unsigned HOST_WIDE_INT),
@@ -790,7 +791,7 @@ extern void check_function_arguments_recurse (void (*)
 					      unsigned HOST_WIDE_INT);
 extern bool check_builtin_function_arguments (location_t, vec<location_t>,
 					      tree, int, tree *);
-extern void check_function_format (tree, int, tree *);
+extern void check_function_format (const_tree, const_tree, int, tree *);
 extern tree handle_unused_attribute (tree *, tree, tree, int, bool *);
 extern tree handle_format_attribute (tree *, tree, tree, int, bool *);
 extern tree handle_format_arg_attribute (tree *, tree, tree, int, bool *);
diff --git a/gcc/c-family/c-format.c b/gcc/c-family/c-format.c
index c19c411..78eff28 100644
--- a/gcc/c-family/c-format.c
+++ b/gcc/c-family/c-format.c
@@ -29,6 +29,12 @@ along with GCC; see the file COPYING3.  If not see
 #include "intl.h"
 #include "langhooks.h"
 #include "c-format.h"
+#include "builtins.h"
+
+#include "backend.h"
+#include "gimple.h"
+#include "ssa.h"
+#include "stor-layout.h"
 
 /* Handle attributes associated with format checking.  */
 
@@ -44,9 +50,16 @@ enum format_type { printf_format_type, asm_fprintf_format_type,
 
 struct function_format_info
 {
+  built_in_function fncode;
   int format_type;			/* type of format (printf, scanf, etc.) */
   unsigned HOST_WIDE_INT format_num;	/* number of format argument */
   unsigned HOST_WIDE_INT first_arg_num;	/* number of first arg (zero for varargs) */
+  /* The destination object size or -1 if unknown.  */
+  unsigned HOST_WIDE_INT objsize;
+
+  /* True for functions whose output is bounded by a size argument
+     (e.g., snprintf and vsnprintf).  */
+  bool bounded;
 };
 
 static bool decode_format_attr (tree, function_format_info *, int);
@@ -65,6 +78,16 @@ static int first_target_format_type;
 static const char *format_name (int format_num);
 static int format_flags (int format_num);
 
+struct format_check_results;
+
+static void
+check_format_length (location_t,
+		     format_check_results *,
+		     const function_format_info *,
+		     const format_char_info *,
+		     const char *, size_t, size_t,
+		     const conversion_spec *, tree);
+
 /* Given a string S of length LINE_WIDTH, find the visual column
    corresponding to OFFSET bytes.   */
 
@@ -378,6 +401,542 @@ decode_format_attr (tree args, function_format_info *info, int validated_p)
 
   return true;
 }
+
+/* Description of a conversion specification.  */
+struct conversion_spec
+{
+  /* A bitmap of flags, one for each character.  */
+  int flags [256 / sizeof (int)];
+  int width;             /* Numeric width.  */
+  int precision;         /* Numeric precision.  */
+
+  tree star_width;       /* Width specified via the '*' character.  */
+  tree star_precision;   /* Precision specified via the asterisk.  */
+
+  format_lengths modifier;   /* Length modifier.  */
+  char specifier;            /* Format specifier character.  */
+
+  unsigned have_width: 1;       /* Numeric width was given.  */
+  unsigned have_precision: 1;   /* Numeric precision was given.  */
+
+  /* Return True when a the format flag CHR has been used.  */
+  bool get_flag (char chr) const
+  {
+    unsigned char c = chr & 0xff;
+    return flags [c / (CHAR_BIT * sizeof *flags)]
+      & (1 << (c % (CHAR_BIT * sizeof *flags)));
+  }
+
+  /* Make a record of the format flag CHR having been used.  */
+  void set_flag (char chr)
+  {
+    unsigned char c = chr & 0xff;
+    flags [c / (CHAR_BIT * sizeof *flags)]
+      |= (1 << (c % (CHAR_BIT * sizeof *flags)));
+  }
+};
+
+/* Return the logarithm of X in BASE.  */
+
+static int
+ilog (unsigned HOST_WIDE_INT x, int base)
+{
+  int res = 0;
+  do {
+    ++res;
+    x /= base;
+  } while (x);
+  return res;
+}
+
+/* Return the logarithm of tree node X in BASE, incremented by 1 when
+   the optional PLUS sign is True, plus the length of the octal ('0')
+   or hexadecimal ('0x') prefix when PREFIX is True.  Return -1 when
+   X cannot be represented.  */
+
+static int
+tree_digits (tree x, int base, bool plus, bool prefix)
+{
+  unsigned HOST_WIDE_INT absval;
+
+  int res;
+
+  if (TYPE_UNSIGNED (TREE_TYPE (x)))
+    {
+      if (tree_fits_uhwi_p (x))
+	{
+	  absval = tree_to_uhwi (x);
+	  res = plus;
+	}
+      else
+	return -1;
+    }
+  else
+    {
+      if (tree_fits_shwi_p (x))
+	{
+	  HOST_WIDE_INT i = tree_to_shwi (x);
+	  if (i < 0)
+	    {
+	      absval = -i;
+	      res = 1;
+	    }
+	  else
+	    {
+	      absval = i;
+	      res = plus;
+	    }
+	}
+      else
+	return -1;
+    }
+
+  res += ilog (absval, base);
+
+  if (prefix && absval)
+    {
+      if (base == 8)
+	res += 1;
+      else if (base == 16)
+	res += 2;
+    }
+
+  return res;
+}
+
+/* Return a range representing the minimum and maximum number of bytes
+   that the conversion specification SPEC will write on output for the
+   integer argument ARG.  */
+
+static format_char_info::fmtresult
+format_integer (const conversion_spec *spec, tree arg)
+{
+  /* Set WIDTH and PRECISION to either the values in the format
+     specification or to zero.  */
+  int width = spec->have_width ? spec->width : 0;
+  int prec = spec->have_precision ? spec->precision : 0;
+
+  if (spec->star_width)
+    width = (TREE_CODE (spec->star_width) == INTEGER_CST)
+            ? tree_to_shwi (spec->star_width) : 0;
+
+  if (spec->star_precision)
+    prec = (TREE_CODE (spec->star_precision) == INTEGER_CST)
+           ? tree_to_shwi (spec->star_precision) : 0;
+
+  /* The minimum and maximum number of bytes produced by the directive.  */
+  format_char_info::fmtresult res;
+
+  bool sign = spec->specifier == 'd' || spec->specifier == 'i';
+  tree type = NULL_TREE;
+
+  switch (spec->modifier)
+    {
+    case FMT_LEN_none:
+      type = sign ? integer_type_node : unsigned_type_node;
+      break;
+
+    case FMT_LEN_h:
+      type = sign ? short_integer_type_node : short_unsigned_type_node;
+      break;
+
+    case FMT_LEN_hh:
+      type = sign ? signed_char_type_node : unsigned_char_type_node;
+      break;
+
+    case FMT_LEN_l:
+      type = sign ? long_integer_type_node : long_unsigned_type_node;
+      break;
+
+    case FMT_LEN_ll:
+      type = sign ? long_integer_type_node : long_unsigned_type_node;
+      break;
+
+    case FMT_LEN_z:
+      type = sign ? ptrdiff_type_node : size_type_node;
+      break;
+
+    case FMT_LEN_t:
+      type = sign ? ptrdiff_type_node : size_type_node;
+      break;
+
+    case FMT_LEN_j:
+      type = sign ? ptrdiff_type_node : size_type_node;
+      break;
+
+    case FMT_LEN_H:
+    case FMT_LEN_D:
+    case FMT_LEN_DD:
+    case FMT_LEN_MAX:
+      /* FIXME: Implement this. */
+      res.min = res.max = -1;
+      return res;
+
+    default:
+      gcc_unreachable ();
+    }
+
+  /* A type of the argument to the directive, either deduced from
+     the actual non-constant argument if one is known, or from
+     the directive itself when none has been provided because it's
+     a va_list.  */
+  tree argtype = NULL_TREE;
+
+  /* The argument is likely unbounded (i.e., the range of its values
+     beyond the limits of its type is likely unknown.  */
+  res.bounded = false;
+
+  if (!arg)
+    {
+      /* When the argument has not been provided, use the type of
+	 the directive's argument as an approximation.  This will
+	 result in false positives for directives like %i with
+	 arguments with smaller precision (such as short or char).  */
+      argtype = type;
+    }
+  else if (TREE_CODE (arg) == INTEGER_CST)
+    res.bounded = true;
+  else
+    {
+      /* Determine the type of the provided non-constant argument.  */
+      if (TREE_CODE (arg) == NOP_EXPR)
+	arg = TREE_OPERAND (arg, 0);
+      else if (TREE_CODE (arg) == CONVERT_EXPR)
+	arg = TREE_OPERAND (arg, 0);
+      if (TREE_CODE (arg) == COMPONENT_REF)
+	arg = TREE_OPERAND (arg, 1);
+
+      argtype = TREE_TYPE (arg);
+    }
+
+  if (argtype)
+    {
+      tree argmin = NULL_TREE;
+      tree argmax = NULL_TREE;
+
+      if (arg && TREE_CODE (arg) == SSA_NAME)
+	{
+	  /* Try to determine the range of values of the argument.  */
+	  wide_int min, max;
+	  enum value_range_type range_type = get_range_info (arg, &min, &max);
+	  if (range_type == VR_RANGE)
+	    {
+	      argmin = build_int_cst (argtype, wi::fits_uhwi_p (min)
+				      ? min.to_uhwi () : min.to_shwi ());
+	      argmax = build_int_cst (argtype, wi::fits_uhwi_p (max)
+				      ? max.to_uhwi () : max.to_shwi ());
+
+	      /* The argument is bounded by the range of values
+		 determined by VRP.  */
+	      res.bounded = true;
+	    }
+	  else if (range_type == VR_ANTI_RANGE)
+	    {
+	      /* Handle anti-ranges if/when bug 71690 is ever resolved.  */
+	    }
+	}
+
+      if (!argmin)
+	{
+	  /* For an unknown argument (e.g., one passed to a vararg
+	     function) or one whose value range cannot be determined,
+	     create a T_MIN constant if the argument's type is signed
+	     and T_MAX otherwise, and use those to compute the range
+	     of bytes that the directive can output.  */
+	  argmin = build_int_cst (argtype, 1);
+
+	  int typeprec = TYPE_PRECISION (type);
+	  int argprec = TYPE_PRECISION (argtype);
+
+	  if (argprec < typeprec)
+	    {
+	      if (TYPE_UNSIGNED (argtype))
+		argmax = build_all_ones_cst (argtype);
+	      else
+		argmax = fold_build2 (LSHIFT_EXPR, argtype, integer_one_node,
+				      build_int_cst (integer_type_node,
+						     argprec - 1));
+	    }
+	  else
+	    {
+	      argmax = fold_build2 (LSHIFT_EXPR, type, integer_one_node,
+				    build_int_cst (integer_type_node,
+						   typeprec - 1));
+	    }
+	}
+
+      /* Recursively compute the minimum and maximum from the known range,
+	 taking care to swap them if the lower bound results in longer
+	 output than the upper bound (e.g., in the range [-1, 0].  */
+      res.min = format_integer (spec, argmin).min;
+      res.max = format_integer (spec, argmax).max;
+      if (res.max < res.min)
+	{
+	  int tmp = res.max;
+	  res.max = res.min;
+	  res.min = tmp;
+	}
+      return res;
+    }
+
+  /* Base to format the number in.  */
+  int base = 10;
+  /* True when a signed conversion is preceded by a sign or space.  */
+  bool maybesign = false;
+
+  switch (spec->specifier)
+    {
+    case 'd':
+    case 'i':
+      /* Space is only effective for signed conversions.  */
+      maybesign = spec->get_flag (' ');
+    case 'u':
+      break;
+    case 'o':
+      base = 8;
+      break;
+    case 'X':
+    case 'x':
+      base = 16;
+      break;
+    default:
+      gcc_unreachable ();
+    }
+
+  /* Convert the argument to the type of the directive.  */
+  arg = fold_convert (type, arg);
+
+  maybesign |= spec->get_flag ('+');
+  int len = tree_digits (arg, base, maybesign, spec->get_flag ('#'));
+
+  if (len < prec)
+    len = prec;
+
+  if (len < width)
+    len = width;
+
+  res.max = len;
+  res.min = res.max;
+  res.bounded = true;
+
+  return res;
+}
+
+/* Return a range representing the minimum and maximum number of bytes
+   that the conversion specification SPEC will write on output for the
+   floating argument ARG.  */
+
+static format_char_info::fmtresult
+format_floating (const conversion_spec *spec, tree arg)
+{
+  /* Set WIDTH and PRECISION to either the values in the format
+     specification or to zero.  */
+  int width = spec->have_width ? spec->width : 0;
+  int prec = spec->have_precision ? spec->precision : -1;
+
+  if (spec->star_width)
+    width = (TREE_CODE (spec->star_width) == INTEGER_CST)
+            ? tree_to_shwi (spec->star_width) : 0;
+
+  if (spec->star_precision)
+    prec = (TREE_CODE (spec->star_precision) == INTEGER_CST)
+            ? tree_to_shwi (spec->star_precision) : 0;
+
+  tree type = arg ? TREE_TYPE (arg) : NULL_TREE;
+
+  switch (spec->modifier)
+    {
+    case FMT_LEN_none:
+      if (!type)
+	type = double_type_node;
+      break;
+
+    case FMT_LEN_L:
+      if (!type)
+	type = long_double_type_node;
+      break;
+
+    default:
+      gcc_unreachable ();
+    }
+
+  /* The minimum and maximum number of bytes produced by the directive.  */
+  format_char_info::fmtresult res;
+
+  int expdigs = -1;    /* Number of exponent digits or -1 when unknown.  */
+  int negative = -1;   /* 1 when arg < 0, 0 when arg >= 0, -1 when unknown.  */
+
+  if (arg && TREE_CODE (arg) == REAL_CST)
+    {
+      expdigs = ilog (real_exponent (TREE_REAL_CST_PTR (arg)), 10);
+      negative = real_isneg (TREE_REAL_CST_PTR (arg));
+    }
+  else if (REAL_MODE_FORMAT (TYPE_MODE (type))->b == 2)
+    {
+      /* Compute T_MAX_EXP for base 2.  */
+      const double log10_2 = .30102999566398119521;
+      expdigs = REAL_MODE_FORMAT (TYPE_MODE (type))->emax * log10_2;
+    }
+
+  int logexpdigs = ilog (expdigs, 10);
+
+  switch (spec->specifier)
+    {
+    case 'A':
+    case 'a':
+      /* The minimum output is "0x.p+0".  */
+      res.min = 6 + (0 < prec ? prec : 0);
+      /* FIXME: Figure out the maximum.  */
+      res.max = -1;
+      if (res.min < width)
+	res.min = width;
+      break;
+
+    case 'E':
+    case 'e':
+      /* The minimum output is "[-+]1.234567e+00" for an IEEE double
+	 regardless of the value of the actual argument. */
+      res.min = (0 < negative || spec->get_flag ('+') || spec->get_flag (' '))
+	+ 1 /* unit */ + (prec < 0 ? 7 : prec ? prec + 1 : 0)
+	+ 2 /* e+ */ + (logexpdigs < 2 ? 2 : logexpdigs);
+      /* The maximum output is "-1.234567e+123" for a double and one more
+	 byte for a large exponent for a long louble.  */
+      res.max = negative < 0 ? res.min + 2 + (spec->get_flag ('L')) : res.min;
+      if (res.min < width)
+	res.min = width;
+      if (res.max < width)
+	res.max = width;
+      break;
+
+    case 'F':
+    case 'f':
+      /* The minimum output is "1.234567" regardless of the value
+	 of the actual argument. */
+      res.min = 2 + (prec < 0 ? 6 : prec);
+      /* The maximum depends on the magnitude of the value but it's
+	 at most 316 bytes for double and 4940 for long double, plus
+	 precision if non-negative, or 6.  */
+      /* res.max = (spec->get_flag ('L') ? 4934 : 310) */
+      /* 	+ (prec < 0 ? 6 : prec ? prec + 1 : 0); */
+      res.max = expdigs + (prec < 0 ? 6 : prec ? prec + 1 : 0);
+      break;
+
+    case 'G':
+    case 'g':
+      /* Treat this the same as '%F' for now even though that's
+	 inaccurate.  */
+      res.min = 2 + (prec < 0 ? 6 : prec);
+      res.max = (spec->get_flag ('L') ? 4934 : 310)
+	+ (prec < 0 ? 6 : prec ? prec + 1 : 0);
+      break;
+
+    default:
+      gcc_unreachable ();
+    }
+
+  /* The argument is only considered bounded when the range of output
+     bytes is exact.  */
+  res.bounded = res.min == res.max;
+  return res;
+}
+
+/* Return the minimum and maximum number of characters formatted
+   by the '%c' and '%s' format directives and ther wide character
+   forms.  */
+
+static format_char_info::fmtresult
+format_string (const conversion_spec *spec, tree arg)
+{
+  int width = spec->have_width ? spec->width : 0;
+  int prec = spec->have_precision ? spec->precision : -1;
+
+  if (spec->star_width)
+    width = (TREE_CODE (spec->star_width) == INTEGER_CST)
+            ? tree_to_shwi (spec->star_width) : 0;
+
+  if (spec->star_precision)
+    prec = (TREE_CODE (spec->star_precision) == INTEGER_CST)
+           ? tree_to_shwi (spec->star_precision) : -1;
+
+  format_char_info::fmtresult res;
+
+  /* The argument is likely unbounded (i.e., its length is likely
+     unknown.  */
+  res.bounded = false;
+
+  /* The number of bytes formatted.  This applies to both '%s' and
+     '%ls' where precision and width are in converted characters
+     (i.e., bytes).  */
+  int nbytes;
+
+  if (spec->specifier == 'c')
+    {
+      if (spec->modifier == FMT_LEN_l)
+	{
+	  int nul = arg && TREE_CODE (arg) == INTEGER_CST
+	    ? integer_zerop (arg) : -1;
+
+	  /* A '%lc' directive is the same as '%ls' for a two element
+	     wide string character with the second element of NUL, so
+	     when the character is unknown the minimum number of bytes
+	     is the smaller of either 0 (at level 1) or 1 (at level 2)
+	     and WIDTH, and the maximum is MB_CUR_MAX in the selected
+	     locale, which is unfortunately, unknown.  */
+	  res.min = 0 < width ? width : 1 < warn_format_length ? nul < 1: !nul;
+	  res.max = -1;
+	  return res;
+	}
+
+      /* A plain '%c' directive.  */
+      nbytes = 1;
+    }
+  else if (tree slen = arg ? c_strlen (arg, 1) : NULL_TREE)
+    {
+      /* A '%s' directive with a constant string.  */
+      nbytes = tree_to_shwi (slen);
+      if (0 <= prec && prec < nbytes)
+	nbytes = prec;
+
+      if (spec->modifier == FMT_LEN_l)
+	{
+	  /* For a '%ls' directive the minimum number of bytes is
+	     the greater of WIDTH and the string length, and the
+	     maximum is either PRECISION when specified or
+	     MB_CUR_MAX * length, which is unknown, so set it
+	     to -1.  */
+	  res.min = nbytes < width ? width : nbytes;
+	  /* FIXME: Be smarter about computing the maximum.  Scan
+	     the wide string for any 8-bit characters and if it
+	     contains none, use its length for the maximum.  */
+	  res.max = 0 <= prec ? prec : -1;
+
+	  res.bounded = -1 < res.max;
+	  return res;
+	}
+    }
+  else
+    {
+      /* For a '%s' and '%ls' directive with a non-constant string,
+	 the minimum number of characters is the greater of WIDTH
+	 and either 0 in mode 1 or the smaller of PRECISION and 1
+	 in mode 2, and the maximum is PRECISION or -1 to disable
+	 tracking.  */
+      res.min = 0 < width ? width : 1 < warn_format_length && prec ? 1 : 0;
+      res.max = 0 <= prec ? prec : -1;
+      res.bounded = res.min == res.max;
+      return res;
+    }
+
+  if (nbytes < width)
+    nbytes = width;
+
+  res.min = res.max = nbytes;
+
+  /* The length is exact.  */
+  res.bounded = true;
+
+  return res;
+}
 
 /* Check a call to a format function against a parameter list.  */
 
@@ -670,21 +1229,27 @@ static const format_flag_pair strfmon_flag_pairs[] =
 };
 
 
+/* FIXME: Suppress the warning to avoid having to change all
+   the format_char_info arrays below and to reduce the footprint
+   of the patch until it's been reviewed and accepted.  */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
+
 static const format_char_info print_char_table[] =
 {
   /* C89 conversion specifiers.  */
-  { "di",  0, STD_C89, { T89_I,   T99_SC,  T89_S,   T89_L,   T9L_LL,  TEX_LL,  T99_SST, T99_PD,  T99_IM,  BADLEN,  BADLEN,  BADLEN  }, "-wp0 +'I",  "i",  NULL },
-  { "oxX", 0, STD_C89, { T89_UI,  T99_UC,  T89_US,  T89_UL,  T9L_ULL, TEX_ULL, T99_ST,  T99_UPD, T99_UIM, BADLEN,  BADLEN,  BADLEN }, "-wp0#",     "i",  NULL },
-  { "u",   0, STD_C89, { T89_UI,  T99_UC,  T89_US,  T89_UL,  T9L_ULL, TEX_ULL, T99_ST,  T99_UPD, T99_UIM, BADLEN,  BADLEN,  BADLEN }, "-wp0'I",    "i",  NULL },
-  { "fgG", 0, STD_C89, { T89_D,   BADLEN,  BADLEN,  T99_D,   BADLEN,  T89_LD,  BADLEN,  BADLEN,  BADLEN,  TEX_D32, TEX_D64, TEX_D128 }, "-wp0 +#'I", "",   NULL },
-  { "eE",  0, STD_C89, { T89_D,   BADLEN,  BADLEN,  T99_D,   BADLEN,  T89_LD,  BADLEN,  BADLEN,  BADLEN,  TEX_D32, TEX_D64, TEX_D128 }, "-wp0 +#I",  "",   NULL },
-  { "c",   0, STD_C89, { T89_I,   BADLEN,  BADLEN,  T94_WI,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN }, "-w",        "",   NULL },
-  { "s",   1, STD_C89, { T89_C,   BADLEN,  BADLEN,  T94_W,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN }, "-wp",       "cR", NULL },
+  { "di",  0, STD_C89, { T89_I,   T99_SC,  T89_S,   T89_L,   T9L_LL,  TEX_LL,  T99_SST, T99_PD,  T99_IM,  BADLEN,  BADLEN,  BADLEN  }, "-wp0 +'I",  "i",  NULL, format_integer },
+  { "oxX", 0, STD_C89, { T89_UI,  T99_UC,  T89_US,  T89_UL,  T9L_ULL, TEX_ULL, T99_ST,  T99_UPD, T99_UIM, BADLEN,  BADLEN,  BADLEN }, "-wp0#",     "i",  NULL, format_integer },
+  { "u",   0, STD_C89, { T89_UI,  T99_UC,  T89_US,  T89_UL,  T9L_ULL, TEX_ULL, T99_ST,  T99_UPD, T99_UIM, BADLEN,  BADLEN,  BADLEN }, "-wp0'I",    "i",  NULL, format_integer},
+  { "fgG", 0, STD_C89, { T89_D,   BADLEN,  BADLEN,  T99_D,   BADLEN,  T89_LD,  BADLEN,  BADLEN,  BADLEN,  TEX_D32, TEX_D64, TEX_D128 }, "-wp0 +#'I", "",   NULL, format_floating },
+  { "eE",  0, STD_C89, { T89_D,   BADLEN,  BADLEN,  T99_D,   BADLEN,  T89_LD,  BADLEN,  BADLEN,  BADLEN,  TEX_D32, TEX_D64, TEX_D128 }, "-wp0 +#I",  "",   NULL, format_floating },
+  { "c",   0, STD_C89, { T89_I,   BADLEN,  BADLEN,  T94_WI,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN }, "-w",        "",   NULL, format_string },
+  { "s",   1, STD_C89, { T89_C,   BADLEN,  BADLEN,  T94_W,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN }, "-wp",       "cR", NULL, format_string },
   { "p",   1, STD_C89, { T89_V,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN }, "-w",        "c",  NULL },
   { "n",   1, STD_C89, { T89_I,   T99_SC,  T89_S,   T89_L,   T9L_LL,  BADLEN,  T99_SST, T99_PD,  T99_IM,  BADLEN,  BADLEN,  BADLEN }, "",          "W",  NULL },
   /* C99 conversion specifiers.  */
-  { "F",   0, STD_C99, { T99_D,   BADLEN,  BADLEN,  T99_D,   BADLEN,  T99_LD,  BADLEN,  BADLEN,  BADLEN,  TEX_D32, TEX_D64, TEX_D128 }, "-wp0 +#'I", "",   NULL },
-  { "aA",  0, STD_C99, { T99_D,   BADLEN,  BADLEN,  T99_D,   BADLEN,  T99_LD,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN }, "-wp0 +#",   "",   NULL },
+  { "F",   0, STD_C99, { T99_D,   BADLEN,  BADLEN,  T99_D,   BADLEN,  T99_LD,  BADLEN,  BADLEN,  BADLEN,  TEX_D32, TEX_D64, TEX_D128 }, "-wp0 +#'I", "",   NULL, format_floating },
+  { "aA",  0, STD_C99, { T99_D,   BADLEN,  BADLEN,  T99_D,   BADLEN,  T99_LD,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN }, "-wp0 +#",   "",   NULL, format_floating },
   /* X/Open conversion specifiers.  */
   { "C",   0, STD_EXT, { TEX_WI,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN }, "-w",        "",   NULL },
   { "S",   1, STD_EXT, { TEX_W,   BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN,  BADLEN }, "-wp",       "R",  NULL },
@@ -880,6 +1445,8 @@ static const format_char_info monetary_char_table[] =
   { NULL, 0, STD_C89, NOLENGTHS, NULL, NULL, NULL }
 };
 
+#pragma GCC diagnostic pop
+
 /* This must be in the same order as enum format_type.  */
 static const format_kind_info format_types_orig[] =
 {
@@ -984,8 +1551,35 @@ struct format_check_results
   int number_unterminated;
   /* Number of leaves of the format argument that were not counted above.  */
   int number_other;
+  /* Number of characters written by the formatting function, exact,
+     minimum and maximum when an exact number cannot be determined.
+     Setting the minimum to a negative value disables all length
+     tracking for the remainder of the format string.
+     Setting either of the other two members disables the exact or
+     maximum length tracking, respectively, but continues to track
+     the maximum.  */
+  int number_chars;
+  int number_chars_min;
+  int number_chars_max;
+
+  /* True when the range given by NUMBER_CHARS_MIN and NUMBER_CHARS_MAX
+     is the output of all directives is determined to be bounded to some
+     subrange of their types or possible lengths, false otherwise.  */
+  bool bounded;
+
   /* Location of the format string.  */
   location_t format_string_loc;
+
+  /* Increment the number of output characters by N.  */
+  void inc_number_chars (int n = 1) {
+    gcc_assert (0 <= n);
+    if (0 <= number_chars)
+      number_chars += n;
+    if (0 <= number_chars_min)
+      number_chars_min += n;
+    if (0 <= number_chars_max)
+      number_chars_max += n;
+  }
 };
 
 struct format_check_context
@@ -1015,7 +1609,7 @@ format_flags (int format_num)
   gcc_unreachable ();
 }
 
-static void check_format_info (function_format_info *, tree);
+static void check_format_info (function_format_info *, const_tree, tree);
 static void check_format_arg (void *, tree, unsigned HOST_WIDE_INT);
 static void check_format_info_main (format_check_results *,
 				    function_format_info *,
@@ -1069,17 +1663,15 @@ decode_format_type (const char *s)
    attribute themselves.  */
 
 void
-check_function_format (tree attrs, int nargs, tree *argarray)
+check_function_format (const_tree fndecl, const_tree attrs, int nargs, tree *argarray)
 {
-  tree a;
-
   /* See if this function has any format attributes.  */
-  for (a = attrs; a; a = TREE_CHAIN (a))
+  for (const_tree a = attrs; a; a = TREE_CHAIN (a))
     {
       if (is_attribute_p ("format", TREE_PURPOSE (a)))
 	{
 	  /* Yup; check it.  */
-	  function_format_info info;
+	  function_format_info info = function_format_info ();
 	  decode_format_attr (TREE_VALUE (a), &info, /*validated=*/true);
 	  if (warn_format)
 	    {
@@ -1090,7 +1682,7 @@ check_function_format (tree attrs, int nargs, tree *argarray)
 	      int i;
 	      for (i = nargs - 1; i >= 0; i--)
 		params = tree_cons (NULL_TREE, argarray[i], params);
-	      check_format_info (&info, params);
+	      check_format_info (&info, fndecl, params);
 	    }
 	  if (warn_suggest_attribute_format && info.first_arg_num == 0
 	      && (format_types[info.format_type].flags
@@ -1378,26 +1970,155 @@ get_flag_spec (const format_flag_spec *spec, int flag, const char *predicates)
   return NULL;
 }
 
-
 /* Check the argument list of a call to printf, scanf, etc.
    INFO points to the function_format_info structure.
    PARAMS is the list of argument values.  */
 
 static void
-check_format_info (function_format_info *info, tree params)
+check_format_info (function_format_info *info, const_tree fndecl, tree params)
 {
+  /* Avoid issuing one set of diagnostics during parsing, and another
+     (possibly duplicate) set when expanding printf built-ins.  There
+     are tradeoffs between the diagnostics issued during parsing and
+     later: the former doesn't benefit from constant propagation or
+     range information from the VRP pass, and the latter may not have
+     the original types of all the arguments.  As a result, for
+     arguments of narrower types with no range information that are
+     promoted to wider types the diagnostics during parsing can reduce
+     the rate of false positives by assuming the range of values of
+     the original type as opposed to the one after promotion.
+  */
+  const struct restore_on_return
+  {
+    int warn_format_save;
+    int warn_format_length_save;
+
+    restore_on_return ()
+    : warn_format_save (warn_format),
+      warn_format_length_save (warn_format_length)
+    {
+      if (!optimize ^ !currently_expanding_gimple_stmt)
+	warn_format_length = 0;
+      if (currently_expanding_gimple_stmt)
+	warn_format = 0;
+    }
+
+    ~restore_on_return () {
+      warn_format = warn_format_save;
+      warn_format_length = warn_format_length_save;
+    }
+  } save_warn_format_settings;
+
   format_check_context format_ctx;
   unsigned HOST_WIDE_INT arg_num;
   tree format_tree;
   format_check_results res;
+
+  /* Buffer size argument number (snprintf and vsnprintf).  */
+  unsigned idx_size = -1;
+
+  /* Object size argument number (snprintf_chk and vsnprintf_chk).  */
+  unsigned idx_objsize = -1;
+
+  if (fndecl && DECL_BUILT_IN (fndecl)
+      && DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_NORMAL)
+    {
+      /* Save the function code to use in diagnostics.  */
+      info->fncode = DECL_FUNCTION_CODE (fndecl);
+
+      switch (DECL_FUNCTION_CODE (fndecl))
+	{
+	case BUILT_IN_SNPRINTF:
+	  idx_size = 2;
+	  info->bounded = true;
+	  break;
+
+	case BUILT_IN_SNPRINTF_CHK:
+	  idx_size = 2;
+	  idx_objsize = 4;
+	  info->bounded = true;
+	  break;
+
+	case BUILT_IN_SPRINTF_CHK:
+	  idx_objsize = 3;
+	  break;
+
+	case BUILT_IN_VSNPRINTF:
+	  idx_size = 2;
+	  info->bounded = true;
+	  break;
+
+	case BUILT_IN_VSNPRINTF_CHK:
+	  idx_size = 2;
+	  idx_objsize = 4;
+	  info->bounded = true;
+	  break;
+
+	case BUILT_IN_VSPRINTF_CHK:
+	  idx_objsize = 3;
+	  break;
+
+	default:
+	  break;
+	}
+    }
+
+  /* The size of the destination as in snprintf(dest, size, ...).  */
+  unsigned HOST_WIDE_INT dstsize = ~(unsigned HOST_WIDE_INT)0;
+
+  /* The size of the destination determined by __builtin_object_size.  */
+  unsigned HOST_WIDE_INT objsize = ~(unsigned HOST_WIDE_INT)0;
+
   /* Skip to format argument.  If the argument isn't available, there's
      no work for us to do; prototype checking will catch the problem.  */
   for (arg_num = 1; ; ++arg_num)
     {
       if (params == 0)
 	return;
+
+      /* Assume the format string argument never appears before the object
+	 size argument in any of the checked signatures.  */
       if (arg_num == info->format_num)
-	break;
+	  break;
+
+      if (arg_num == idx_size)
+	{
+	  tree size = TREE_VALUE (params);
+	  if (TREE_CODE (size) == INTEGER_CST)
+	    {
+	      dstsize = tree_to_uhwi (size);
+	      if (dstsize > ~(unsigned HOST_WIDE_INT)0 / 2)
+		warning_at (EXPR_LOC_OR_LOC (params, input_location),
+			    0, "specified destination size %qwu too large",
+			    dstsize);
+	    }
+	  else if (TREE_CODE (size) == SSA_NAME)
+	    {
+	      /* Try to determine the range of values of the argument
+		 and use the greater of the two at -Wformat-level 1 and
+		 the smaller of them at level 2.  */
+	      wide_int min, max;
+	      enum value_range_type range_type
+		= get_range_info (size, &min, &max);
+	      if (range_type == VR_RANGE)
+		{
+		  dstsize = warn_format_length < 2
+		    ? wi::fits_uhwi_p (max) ? max.to_uhwi () : max.to_shwi ()
+		    : wi::fits_uhwi_p (min) ? min.to_uhwi () : min.to_shwi ();
+		}
+	    }
+	}
+
+      if (arg_num == idx_objsize)
+	{
+	  /* This is in all likelihood the result of __builtin_object_size
+	     which should be constant but there's a small change that it
+	     isn't.  */
+	  tree size = TREE_VALUE (params);
+	  if (TREE_CODE (size) == INTEGER_CST)
+	    objsize = tree_to_uhwi (size);
+	}
+
       params = TREE_CHAIN (params);
     }
   format_tree = TREE_VALUE (params);
@@ -1405,6 +2126,28 @@ check_format_info (function_format_info *info, tree params)
   if (format_tree == 0)
     return;
 
+  if (info->bounded && !dstsize)
+    {
+      /* As a special case, bounded function allow the explicitly
+	 specified destination size argument to be zero as a request
+	 to determine the number of bytes on output without actually
+	 writing any output.  Disable length checking for those.  */
+      info->objsize = ~(unsigned HOST_WIDE_INT)0;
+    }
+  else
+    {
+      /* Set the object size to the smaller of the two arguments of both
+	 have been specified and they're not equal.  */
+      info->objsize = dstsize < objsize ? dstsize : objsize;
+
+      if (dstsize != ~(unsigned HOST_WIDE_INT)0 && objsize < dstsize)
+	{
+	  warning (0, "specified destination size %qwu exceeds "
+		   "the size of the object %qwu", dstsize, objsize);
+	  
+	}
+    }
+
   res.number_non_literal = 0;
   res.number_extra_args = 0;
   res.extra_arg_loc = UNKNOWN_LOCATION;
@@ -1414,6 +2157,10 @@ check_format_info (function_format_info *info, tree params)
   res.number_unterminated = 0;
   res.number_other = 0;
   res.format_string_loc = input_location;
+  res.number_chars = 0;
+  res.number_chars_min = 0;
+  res.number_chars_max = 0;
+  res.bounded = true;
 
   format_ctx.res = &res;
   format_ctx.info = info;
@@ -1688,6 +2435,42 @@ check_format_arg (void *ctx, tree format_tree,
 			  params, arg_num, fwt_pool);
 }
 
+/* Given the formatting result described by RES, return the number
+   of bytes remaining in the destination buffer whose size is OBJSIZE.  */
+
+static inline size_t
+bytes_remaining (const function_format_info *info,
+		 const format_check_results *res)
+{
+  size_t navail = info->objsize;
+
+  if (1 < warn_format_length || res->bounded)
+    {
+      /* At level 2, or when all directives output an exact number
+	 of bytes or when their arguments were bounded by known
+	 ranges, use the greater of the two byte counters if it's
+	 valid to compute the result.  */
+      if (0 <= res->number_chars_max)
+	navail -= res->number_chars_max;
+      else if (0 <= res->number_chars)
+	navail -= res->number_chars;
+      else if (0 <= res->number_chars_min)
+	navail -= res->number_chars_min;
+    }
+  else
+    {
+      /* At level 1 use the smaller of the byte counters to compute
+	 the result.  */
+      if (0 <= res->number_chars)
+	navail -= res->number_chars;
+      else if (0 <= res->number_chars_min)
+	navail -= res->number_chars_min;
+      else if (0 <= res->number_chars_max)
+	navail -= res->number_chars_max;
+    }
+
+  return navail;
+}
 
 /* Do the main part of checking a call to a format function.  FORMAT_CHARS
    is the NUL-terminated format string (which at this point may contain
@@ -1725,7 +2508,7 @@ check_format_info_main (format_check_results *res,
       enum format_lengths length_chars_val = FMT_LEN_none;
       enum format_std_version length_chars_std = STD_C89;
       int format_char;
-      tree cur_param;
+      tree cur_param = NULL_TREE;
       tree wanted_type;
       int main_arg_num = 0;
       tree main_arg_params = 0;
@@ -1738,13 +2521,53 @@ check_format_info_main (format_check_results *res,
       format_wanted_type *last_wanted_type = NULL;
       const format_length_info *fli = NULL;
       const format_char_info *fci = NULL;
+      conversion_spec cvtspec = conversion_spec ();
       char flag_chars[256];
       int alloc_flag = 0;
       int scalar_identity_flag = 0;
       const char *format_start;
 
       if (*format_chars++ != '%')
-	continue;
+	{
+	  /* There must always be at least one byte of space in the buffer
+	     for the terminating NUL that's appended after the format string
+	     has been processed.  */
+	  size_t navail = bytes_remaining (info, res);
+	  /* The destination will have already overflowed if the number of
+	     bytes has wrapped around zero.  */
+	  bool overflowed = SIZE_MAX / 2 <= navail;
+	  if (!overflowed && navail < 1)
+	    {
+	      unsigned long off
+		= (unsigned long)(format_chars - orig_format_chars);
+
+	      location_t loc
+		= location_from_offset (format_string_loc, off);
+
+	      /* Differentiate between an exact and inexact buffer overflow
+		 or truncation.  */
+	      const char *fmtstr;
+	      if (res->number_chars < 0)
+		fmtstr = info->bounded
+		  ? "output may be truncated at or before format character "
+		    "%qc at offset %qlu past the end of a region of size %qlu"
+		  : "writing format character %qc at offset %qlu "
+		    "in a region of size %qlu";
+	      else
+		fmtstr = info->bounded
+		  ? "output truncated at format character %qc at offset %qlu "
+		    "just past the end of a region of size %qlu"
+		  : "writing format character %qc at offset %qlu "
+		    "just past the end of a region of size %qlu";
+	      warning_at (loc, OPT_Wformat_length_, fmtstr,
+			  format_chars [-1], off - 1,
+			  (unsigned long)info->objsize);
+	    }
+
+	  res->inc_number_chars ();
+	  continue;
+	}
+
       if (*format_chars == 0)
 	{
           warning_at (location_from_offset (format_string_loc,
@@ -1755,9 +2578,16 @@ check_format_info_main (format_check_results *res,
 	}
       if (*format_chars == '%')
 	{
+	  res->inc_number_chars ();
 	  ++format_chars;
 	  continue;
 	}
+
+      /* Save the place of the first character of the format conversion
+	 specification so that the full directive can be printed in
+	 diagnostics.  */
+      const char *cvtbeg = format_chars;
+
       flag_chars[0] = 0;
 
       if ((fki->flags & (int) FMT_FLAG_USE_DOLLAR) && has_operand_number != 0)
@@ -1805,6 +2635,8 @@ check_format_info_main (format_check_results *res,
 	      i = strlen (flag_chars);
 	      flag_chars[i++] = *format_chars;
 	      flag_chars[i] = 0;
+
+	      cvtspec.set_flag (*format_chars);
 	    }
 	  if (s->skip_next_char)
 	    {
@@ -1859,6 +2691,7 @@ check_format_info_main (format_check_results *res,
                   else
                     {
                       cur_param = TREE_VALUE (params);
+		      cvtspec.star_width = cur_param;
                       if (has_operand_number <= 0)
                         {
                           params = TREE_CHAIN (params);
@@ -1892,23 +2725,25 @@ check_format_info_main (format_check_results *res,
 	      /* Possibly read a numeric width.  If the width is zero,
 		 we complain if appropriate.  */
 	      int non_zero_width_char = FALSE;
-	      int found_width = FALSE;
+	      cvtspec.have_width = FALSE;
+	      const char *widthbeg = format_chars;
 	      while (ISDIGIT (*format_chars))
 		{
-		  found_width = TRUE;
+		  cvtspec.have_width = TRUE;
 		  if (*format_chars != '0')
 		    non_zero_width_char = TRUE;
 		  ++format_chars;
 		}
-	      if (found_width && !non_zero_width_char &&
+	      if (cvtspec.have_width && !non_zero_width_char &&
 		  (fki->flags & (int) FMT_FLAG_ZERO_WIDTH_BAD))
 		warning_at (format_string_loc, OPT_Wformat_,
 			    "zero width in %s format", fki->name);
-	      if (found_width)
+	      if (cvtspec.have_width)
 		{
 		  i = strlen (flag_chars);
 		  flag_chars[i++] = fki->width_char;
 		  flag_chars[i] = 0;
+		  cvtspec.width = strtol (widthbeg, 0, 10);
 		}
 	    }
 	}
@@ -1925,8 +2760,12 @@ check_format_info_main (format_check_results *res,
 					      format_chars - orig_format_chars),
 			OPT_Wformat_,
 			"empty left precision in %s format", fki->name);
+
+	  const char *precbeg = format_chars;
 	  while (ISDIGIT (*format_chars))
 	    ++format_chars;
+	  cvtspec.precision = strtol (precbeg, 0, 10);
+	  cvtspec.have_precision = 1;
 	}
 
       /* Read any format precision, possibly * or *m$.  */
@@ -1970,6 +2809,7 @@ check_format_info_main (format_check_results *res,
                   else
                     {
                       cur_param = TREE_VALUE (params);
+		      cvtspec.star_precision = cur_param;
                       if (has_operand_number <= 0)
                         {
                           params = TREE_CHAIN (params);
@@ -2006,8 +2846,15 @@ check_format_info_main (format_check_results *res,
 						  format_chars - orig_format_chars),
 			    OPT_Wformat_,
 			    "empty precision in %s format", fki->name);
+
+	      const char *precbeg = format_chars;
 	      while (ISDIGIT (*format_chars))
 		++format_chars;
+	      if (format_chars - precbeg)
+		{
+		  cvtspec.precision = strtol (precbeg, 0, 10);
+		  cvtspec.have_precision = TRUE;
+		}
 	    }
 	}
 
@@ -2047,7 +2894,7 @@ check_format_info_main (format_check_results *res,
 	{
 	  while (fli->name != 0
  		 && strncmp (fli->name, format_chars, strlen (fli->name)))
-	      fli++;
+	    fli++;
 	  if (fli->name != 0)
 	    {
  	      format_chars += strlen (fli->name);
@@ -2057,6 +2904,7 @@ check_format_info_main (format_check_results *res,
 		  length_chars = fli->double_name;
 		  length_chars_val = fli->double_index;
 		  length_chars_std = fli->double_std;
+		  cvtspec.modifier = fli->double_index;
 		}
 	      else
 		{
@@ -2064,6 +2912,7 @@ check_format_info_main (format_check_results *res,
 		  length_chars_val = fli->index;
 		  length_chars_std = fli->std;
 		  scalar_identity_flag = fli->scalar_identity_flag;
+		  cvtspec.modifier = fli->index;
 		}
 	      i = strlen (flag_chars);
 	      flag_chars[i++] = fki->length_code_char;
@@ -2106,6 +2955,7 @@ check_format_info_main (format_check_results *res,
 	    }
 	}
 
+      /* Read the conversion specifier character.  */
       format_char = *format_chars;
       if (format_char == 0
 	  || (!(fki->flags & (int) FMT_FLAG_FANCY_PERCENT_OK)
@@ -2117,6 +2967,7 @@ check_format_info_main (format_check_results *res,
 		      "conversion lacks type at end of format");
 	  continue;
 	}
+
       format_chars++;
       fci = fki->conversion_specs;
       while (fci->format_chars != 0
@@ -2148,6 +2999,8 @@ check_format_info_main (format_check_results *res,
 			C_STD_NAME (fci->std), format_char, fki->name);
 	}
 
+      cvtspec.specifier = format_char;
+
       /* Validate the individual flags used, removing any that are invalid.  */
       {
 	int d = 0;
@@ -2327,7 +3180,14 @@ check_format_info_main (format_check_results *res,
 
       /* Finally. . .check type of argument against desired type!  */
       if (info->first_arg_num == 0)
-	continue;
+	{
+	  if (warn_format_length)
+	    check_format_length (format_string_loc, res, info, fci,
+				 cvtbeg, format_chars - cvtbeg,
+				 cvtbeg - orig_format_chars,
+				 &cvtspec, NULL_TREE);
+	  continue;
+	}
       if ((fci->pointer_count == 0 && wanted_type == void_type_node)
 	  || suppressed)
 	{
@@ -2366,7 +3226,7 @@ check_format_info_main (format_check_results *res,
 	    }
 
 	  wanted_type_ptr = &main_wanted_type;
-	  while (fci)
+	  for (const format_char_info *pfci = fci; pfci; )
 	    {
 	      if (params == 0)
                 cur_param = NULL;
@@ -2378,9 +3238,9 @@ check_format_info_main (format_check_results *res,
 
 	      wanted_type_ptr->wanted_type = wanted_type;
 	      wanted_type_ptr->wanted_type_name = wanted_type_name;
-	      wanted_type_ptr->pointer_count = fci->pointer_count + alloc_flag;
+	      wanted_type_ptr->pointer_count = pfci->pointer_count + alloc_flag;
 	      wanted_type_ptr->char_lenient_flag = 0;
-	      if (strchr (fci->flags2, 'c') != 0)
+	      if (strchr (pfci->flags2, 'c') != 0)
 		wanted_type_ptr->char_lenient_flag = 1;
 	      wanted_type_ptr->scalar_identity_flag = 0;
 	      if (scalar_identity_flag)
@@ -2391,9 +3251,9 @@ check_format_info_main (format_check_results *res,
 		wanted_type_ptr->writing_in_flag = 1;
 	      else
 		{
-		  if (strchr (fci->flags2, 'W') != 0)
+		  if (strchr (pfci->flags2, 'W') != 0)
 		    wanted_type_ptr->writing_in_flag = 1;
-		  if (strchr (fci->flags2, 'R') != 0)
+		  if (strchr (pfci->flags2, 'R') != 0)
 		    wanted_type_ptr->reading_from_flag = 1;
 		}
               wanted_type_ptr->kind = CF_KIND_FORMAT;
@@ -2409,19 +3269,25 @@ check_format_info_main (format_check_results *res,
 		first_wanted_type = wanted_type_ptr;
 	      last_wanted_type = wanted_type_ptr;
 
-	      fci = fci->chain;
-	      if (fci)
+	      pfci = pfci->chain;
+	      if (pfci)
 		{
 		  wanted_type_ptr = fwt_pool.allocate ();
 		  arg_num++;
-		  wanted_type = *fci->types[length_chars_val].type;
-		  wanted_type_name = fci->types[length_chars_val].name;
+		  wanted_type = *pfci->types[length_chars_val].type;
+		  wanted_type_name = pfci->types[length_chars_val].name;
 		}
 	    }
 	}
 
       if (first_wanted_type != 0)
         check_format_types (format_string_loc, first_wanted_type);
+
+      if (warn_format_length)
+	check_format_length (format_string_loc, res, info, fci,
+			     cvtbeg, format_chars - cvtbeg,
+			     cvtbeg - orig_format_chars,
+			     &cvtspec, cur_param);
     }
 
   if (format_chars - orig_format_chars != format_length)
@@ -2437,6 +3303,62 @@ check_format_info_main (format_check_results *res,
     }
   if (has_operand_number > 0)
     finish_dollar_format_checking (res, fki->flags & (int) FMT_FLAG_DOLLAR_GAP_POINTER_OK);
+
+  /* Bail early if format length checking is disabled, either
+     via a command line option, or as a result of the size of
+     the destination object not being available, or due to
+     formt directives or arguments encountered during processing
+     that prevent length tracking with any reliability.  */
+  if (!warn_format_length
+      || SIZE_MAX / 2 < info->objsize
+      || res->number_chars_min < 0)
+    return;
+
+  /* Compute the number of available bytes in the destination.  There
+     must always be at least one byte left for the terminating NUL that's
+     appended after the format string has been processed.  */
+  size_t navail = bytes_remaining (info, res);
+
+  /* If the number of available bytes has wrapped around zero
+     the destination has already overflowed and been diagnosed so
+     avoid diagnosing it again.  In the diagnostic, distinguish between
+     a possible overflow ("may write"), a certain overlow somewhere "past
+     the end", and a certain overflow into the byte "just past the end"
+     of the destination.  (Ditto for truncation.)  */
+  bool overflowed = SIZE_MAX / 2 <= navail;
+  if (!overflowed && navail == 0)
+    {
+      location_t loc
+	= location_from_offset (format_string_loc,
+				format_chars - orig_format_chars + 1);
+
+      bool boundrange = res->number_chars_min < 0
+	|| (res->number_chars_min < res->number_chars_max);
+
+      const char* fmtstr = info->bounded
+	? (res->number_chars < 0 && boundrange
+	   ? "output may be truncated while copying format string "
+	     "into a region of size %qlu"
+	   : "output truncated while copying format string "
+	     "into a region of size %qlu")
+	: (res->number_chars < 0 ? boundrange
+	   ? "may write a terminating nul past the end "
+	     "of a region of size %qlu"
+	   : "writing a terminating nul past the end "
+	     "of a region of size %qlu"
+	   : "writing a terminating nul just past the end "
+	     "of a region of size %qlu");
+
+      warning_at (loc, OPT_Wformat_length_, fmtstr,
+		  (unsigned long)info->objsize);
+    }
+
+  /* Help the user figure out how big a buffer they need.  */
+  if (warn_format_length && (overflowed || navail < 1))
+    inform (input_location,
+	    "destination region size is %qwu bytes, minimum required %qwu",
+	    (unsigned HOST_WIDE_INT)info->objsize,
+	    (unsigned HOST_WIDE_INT)(info->objsize - navail + 1));
 }
 
 
@@ -2617,6 +3539,124 @@ check_format_types (location_t loc, format_wanted_type *types)
     }
 }
 
+static void
+check_format_length (location_t                  loc,
+		     format_check_results       *res,
+		     const function_format_info *info,
+		     const format_char_info     *fci,
+		     const char                 *cvtbeg,
+		     size_t                      cvtlen,
+		     size_t                      offset,
+		     const conversion_spec      *cvtspec,
+		     tree                        arg)
+{
+  /* Bail when there is no function to compute the output length,
+     or when the size of the object is too big (i.e., unknown)
+     or when minimum length checking has been disabled.   */
+  if (!fci->fmtfunc
+      || SIZE_MAX / 2 < info->objsize
+      || res->number_chars_min == -1)
+    return;
+
+  /* Compute the (approximate) length of the formatted output.  */
+  format_char_info::fmtresult fmtres = fci->fmtfunc (cvtspec, arg);
+
+  /* The overall result is bounded only if the output of every
+     directive is exact or bounded.  */
+  res->bounded = fmtres.bounded;
+
+  if (fmtres.max < 0)
+    {
+      /* Disable exact and maximum length checking after a failure
+	 to determine the maximum number of characters (for example
+	 for wide characters or wide character strings) but continue
+	 tracking the minimum number of characters.  */
+      res->number_chars_max = -1;
+      res->number_chars = -1;
+    }
+
+  if (fmtres.min < 0)
+    {
+      /* Disable exact length checking after a failure to determine
+	 even the minimum number of characters (it shouldn't happen
+	 except in an error) but keep tracking the minimum and maximum
+	 number of characters.  */
+      res->number_chars = -1;
+      return;
+    }
+
+  /* Compute the number of available bytes in the destination.  There
+     must always be at least one byte of space for the terminating
+     NUL that's appended after the format string has been processed.  */
+  size_t navail = bytes_remaining (info, res);
+
+  /* The destination will have already overflowed if the number of
+     bytes has wrapped around zero.  */
+  bool overflowed = SIZE_MAX / 2 <= navail;
+
+  if (fmtres.min < fmtres.max)
+    {
+      /* The result is a range (i.e., it's inexact).  */
+      if (!overflowed)
+	{
+	  loc = location_from_offset (loc, offset);
+
+	  if (navail < (size_t)fmtres.min)
+	    {
+	      const char* fmtstr = info->bounded
+		? "%<%%%.*s%> directive output truncated %qi bytes "
+		  "into a region of size %qlu"
+		: "%<%%%.*s%> directive writing at least %qi bytes "
+		  "into a region of size %qlu";
+		warning_at (loc, OPT_Wformat_length_, fmtstr,
+			    (int)cvtlen, cvtbeg, fmtres.min,
+			    (unsigned long)navail);
+	    }
+	  else if (navail < (size_t)fmtres.max
+		   && (fmtres.bounded || 1 < warn_format_length))
+	    {
+	      const char* fmtstr = info->bounded
+		? "%<%%%.*s%> directive output may be truncated between "
+		  "%qi and %qi bytes into a region of size %qlu"
+		: "%<%%%.*s%> directive writing between %qi and %qi bytes "
+		  "into a region of size %qlu";
+	      warning_at (loc, OPT_Wformat_length_, fmtstr,
+			  (int)cvtlen, cvtbeg, fmtres.min, fmtres.max,
+			  (unsigned long)navail);
+	    }
+	}
+
+      /* Disable exact length checking but adjust the minimum and maximum.  */
+      res->number_chars = -1;
+      if (res->number_chars_max != -1 && fmtres.max != -1)
+	res->number_chars_max += fmtres.max;
+
+      res->number_chars_min += fmtres.min;
+    }
+  else
+    {
+      if (!overflowed && 0 < fmtres.min && navail < (size_t)fmtres.min)
+	{
+	  loc = location_from_offset (loc, offset);
+	  const char* fmtstr;
+	  if (info->bounded)
+	    fmtstr = 1 < fmtres.min
+	      ? "%<%%%.*s%> directive output truncated while writing "
+	        "%qi bytes into a region of size %qlu"
+	      : "%<%%%.*s%> directive output truncated while writing "
+	        "%qi byte into a region of size %qlu";
+	  else
+	    fmtstr = 1 < fmtres.min
+	      ? "%<%%%.*s%> directive writing %qi bytes "
+	        "into a region of size %qlu"
+	      : "%<%%%.*s%> directive writing %qi byte "
+	        "into a region of size %qlu";
+	  warning_at (loc, OPT_Wformat_length_, fmtstr,
+		      (int)cvtlen, cvtbeg, fmtres.min, (unsigned long)navail);
+	}
+      res->inc_number_chars (fmtres.min);
+    }
+}
 
 /* Give a warning at LOC about a format argument of different type from that
    expected.  WANTED_TYPE is the type the argument should have, possibly
diff --git a/gcc/c-family/c-format.h b/gcc/c-family/c-format.h
index edbd4a1..8a34c8a 100644
--- a/gcc/c-family/c-format.h
+++ b/gcc/c-family/c-format.h
@@ -126,6 +126,7 @@ struct format_type_detail
 #define BADLEN	{ STD_C89, NULL, NULL }
 #define NOLENGTHS	{ BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN, BADLEN }
 
+struct conversion_spec;
 
 /* Structure describing a format conversion specifier (or a set of specifiers
    which act identically), and the length modifiers used with it.  */
@@ -158,6 +159,17 @@ struct format_char_info
      arguments, only POINTER_COUNT, TYPES, and the "c", "R", and "W" flags
      in FLAGS2 are used.  */
   const struct format_char_info *chain;
+
+  /* Function to convert an argument for the format characters
+     in FORMAT_CHARS and return the number of characters.   */
+  struct fmtresult {
+    int max, min;
+    /* True when the range is the result of an argument determined
+       to be bounded to a subrange of its type, false otherwise.  */
+    bool bounded;
+  };
+
+  fmtresult (*fmtfunc)(const conversion_spec *, tree);
 };
 
 
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 761a83b..f2e82e2 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -458,6 +458,11 @@ Wformat-extra-args
 C ObjC C++ ObjC++ Var(warn_format_extra_args) Warning LangEnabledBy(C ObjC C++ ObjC++,Wformat=, warn_format >= 1, 0)
 Warn if passing too many arguments to a function for its format string.
 
+Wformat-length
+C ObjC C++ ObjC++ Warning Alias (Wformat-length=, 1, 0)
+Warn about function calls with format strings that wite past the end
+of the destination region.
+
 Wformat-nonliteral
 C ObjC C++ ObjC++ Var(warn_format_nonliteral) Warning LangEnabledBy(C ObjC C++ ObjC++,Wformat=, warn_format >= 2, 0)
 Warn about format strings that are not literals.
@@ -482,6 +487,11 @@ Wformat=
 C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_format) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall, 1, 0)
 Warn about printf/scanf/strftime/strfmon format string anomalies.
 
+Wformat-length=
+C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_format_length) Warning LangEnabledBy(C ObjC C++ ObjC++,Wformat=, warn_format >= 1, 0)
+Warn about function calls with format strings that wite past the end
+of the destination region.
+
 Wignored-qualifiers
 C C++ Var(warn_ignored_qualifiers) Warning EnabledBy(Wextra)
 Warn whenever type qualifiers are ignored.
diff --git a/gcc/c/c-lang.c b/gcc/c/c-lang.c
index 89954b7..9841787 100644
--- a/gcc/c/c-lang.c
+++ b/gcc/c/c-lang.c
@@ -37,6 +37,8 @@ enum c_language_kind c_language = clk_c;
 #define LANG_HOOKS_INIT c_objc_common_init
 #undef LANG_HOOKS_INIT_TS
 #define LANG_HOOKS_INIT_TS c_common_init_ts
+#undef LANG_HOOKS_CHECK_FORMAT_LENGTH
+#define LANG_HOOKS_CHECK_FORMAT_LENGTH check_function_format
 
 /* Each front end provides its own lang hook initializer.  */
 struct lang_hooks lang_hooks = LANG_HOOKS_INITIALIZER;
diff --git a/gcc/c/c-typeck.c b/gcc/c/c-typeck.c
index a681d76..53288b2 100644
--- a/gcc/c/c-typeck.c
+++ b/gcc/c/c-typeck.c
@@ -3090,7 +3090,7 @@ build_function_call_vec (location_t loc, vec<location_t> arg_loc,
     return error_mark_node;
 
   /* Check that the arguments to the function are valid.  */
-  check_function_arguments (loc, fntype, nargs, argarray);
+  check_function_arguments (loc, fundecl, fntype, nargs, argarray);
 
   if (name != NULL_TREE
       && !strncmp (IDENTIFIER_POINTER (name), "__builtin_", 10))
diff --git a/gcc/cp/call.c b/gcc/cp/call.c
index 729b7eb..f3d397c 100644
--- a/gcc/cp/call.c
+++ b/gcc/cp/call.c
@@ -7578,7 +7578,8 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
       for (j = 0; j < nargs; j++)
 	fargs[j] = maybe_constant_value (argarray[j]);
 
-      check_function_arguments (input_location, TREE_TYPE (fn), nargs, fargs);
+      check_function_arguments (input_location, fn, TREE_TYPE (fn),
+				nargs, fargs);
     }
 
   /* Avoid actually calling copy constructors and copy assignment operators,
diff --git a/gcc/cp/cp-lang.c b/gcc/cp/cp-lang.c
index 8cfee4f..bb8a296 100644
--- a/gcc/cp/cp-lang.c
+++ b/gcc/cp/cp-lang.c
@@ -78,6 +78,8 @@ static tree cxx_enum_underlying_base_type (const_tree);
 #define LANG_HOOKS_EH_RUNTIME_TYPE build_eh_type_type
 #undef LANG_HOOKS_ENUM_UNDERLYING_BASE_TYPE
 #define LANG_HOOKS_ENUM_UNDERLYING_BASE_TYPE cxx_enum_underlying_base_type
+#undef LANG_HOOKS_CHECK_FORMAT_LENGTH
+#define LANG_HOOKS_CHECK_FORMAT_LENGTH check_function_format
 
 /* Each front end provides its own lang hook initializer.  */
 struct lang_hooks lang_hooks = LANG_HOOKS_INITIALIZER;
diff --git a/gcc/cp/typeck.c b/gcc/cp/typeck.c
index f68c2a3..93c9ddb 100644
--- a/gcc/cp/typeck.c
+++ b/gcc/cp/typeck.c
@@ -3641,7 +3641,7 @@ cp_build_function_call_vec (tree function, vec<tree, va_gc> **params,
 
   /* Check for errors in format strings and inappropriately
      null parameters.  */
-  check_function_arguments (input_location, fntype, nargs, argarray);
+  check_function_arguments (input_location, fndecl, fntype, nargs, argarray);
 
   ret = build_cxx_call (function, nargs, argarray, complain);
 
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index aa11209..67c64eb 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -265,7 +265,8 @@ Objective-C and Objective-C++ Dialects}.
 -Wno-div-by-zero -Wdouble-promotion -Wduplicated-cond @gol
 -Wempty-body  -Wenum-compare -Wno-endif-labels @gol
 -Werror  -Werror=* -Wfatal-errors -Wfloat-equal  -Wformat  -Wformat=2 @gol
--Wno-format-contains-nul -Wno-format-extra-args -Wformat-nonliteral @gol
+-Wno-format-contains-nul -Wno-format-extra-args -Wformat-length=2 @gol
+-Wformat-nonliteral @gol
 -Wformat-security  -Wformat-signedness  -Wformat-y2k -Wframe-address @gol
 -Wframe-larger-than=@var{len} -Wno-free-nonheap-object -Wjump-misses-init @gol
 -Wignored-qualifiers  -Wignored-attributes  -Wincompatible-pointer-types @gol
@@ -3773,6 +3774,80 @@ in the case of @code{scanf} formats, this option suppresses the
 warning if the unused arguments are all pointers, since the Single
 Unix Specification says that such unused arguments are allowed.
 
+@item -Wformat-length
+@itemx -Wformat-length=@var{level}
+@opindex Wformat-length
+@opindex Wno-format-length
+@opindex ffreestanding
+@opindex fno-builtin
+@opindex Wformat-length=
+
+The @option{-Wformat-length} option causes GCC to attempt to detect calls
+to formatting functions such as @code{sprintf} that might overflow the
+destination buffer, or bounded functions like @code{snprintf} that result
+in output truncation.  GCC counts the number of bytes that each format
+string and directive within it writes into the provided buffer and, when
+it detects that more bytes that fit in the destination buffer may be output,
+it emits a warning.  Directives whose arguments have values that can be
+determined at compile-time account for the exact number of bytes they write.
+Directives with arguments whose values cannot be determined are processed
+based on heuristics that depend on the @var{level} argument to the option,
+and on optimization.  The default setting of @var{level} is 1.  Level
+@var{1} employs a conservative approach that warns only about calls that
+most likely overflow the buffer or result in output truncation.  At this
+level, numeric arguments to format directives whose values are unknown
+are assumed to have the value of one, and strings of unknown length are
+assumed to have a length of zero.  Numeric argument that are known to
+be bounded to a subrange of their type are assumed to take on the value
+within the range that results in the most bytes on output.  Level @var{2}
+warns also bout calls that may overflow the destination buffer or result
+in truncation given an argument of sufficient length or magnitude.  At
+this level, unknown numeric arguments are assumed to have the minimum
+representable value for signed types with a precision greater than 1,
+and the maximum representable value otherwise.  Unknown string arguments
+are assumed to be 1 character long.  Enabling optimization will in most
+cases improve the accuracy of the warning, although in some cases it may
+also result in false positives.
+
+For example, at level @var{1}, the call to @code{sprintf} below is diagnosed
+because even with both @var{a} and @var{b} equal to zero, the terminating
+NUL character (@code{'\0'}) appended by the function to the destination
+buffer will be written past its end.  Increasing the size of the buffer by
+a single byte is sufficient to avoid the warning.  At level @var{2}, the call
+is again diagnosed, but this time because with @var{a} equal to a 32-bit
+@code{INT_MIN} the first @code{%i} directive will write some of its digits
+beyond the end of the destination buffer.  To make the call safe regardless
+of the values of the two variables the size of the destination buffer must
+be increased to at least 34 bytes.  GCC includes the minimum size of the
+buffer in an inforational note following the warning.
+
+@smallexample
+void f (int a, int b)
+@{
+  char buf [12];
+  sprintf (buf, "a = %i, b = %i\n", a, b);
+@}
+@end smallexample
+
+An alternative to increasing the size of the destination buffer is to
+constrain the range of formatted values.  The maximum length of string
+arguments can be bounded by specifying the precision in the fortmat
+directive.  When numeric arguments of format directives can be assumed
+to be bounded by less than the precision of their type, choosing
+an appropriate length modifier to the format character will reduce
+the minimum buffer size.  For exampe, if @var{a} and @var{b} above can
+be assumed to be within the precision of the @code{short int} type then
+using either the @code{%hi} format directive or casting the argument to
+@code{short} reduces the maximum required size of the buffer to 24 bytes.
+
+@smallexample
+void f (int a, int b)
+@{
+  char buf [23];
+  sprintf (buf, "a = %hi, b = %i\n", a, (short)b);
+@}
+@end smallexample
+
 @item -Wno-format-zero-length
 @opindex Wno-format-zero-length
 @opindex Wformat-zero-length
diff --git a/gcc/genmodes.c b/gcc/genmodes.c
index 788031b..79783fd 100644
--- a/gcc/genmodes.c
+++ b/gcc/genmodes.c
@@ -486,7 +486,7 @@ make_vector_modes (enum mode_class cl, unsigned int width,
 {
   struct mode_data *m;
   struct mode_data *v;
-  char buf[8];
+  char buf[12];
   unsigned int ncomponents;
   enum mode_class vclass = vector_class (cl);
 
diff --git a/gcc/langhooks-def.h b/gcc/langhooks-def.h
index 034b3b7..ff7b4d1 100644
--- a/gcc/langhooks-def.h
+++ b/gcc/langhooks-def.h
@@ -52,6 +52,7 @@ extern void lhd_print_error_function (diagnostic_context *,
 				      const char *, struct diagnostic_info *);
 extern void lhd_set_decl_assembler_name (tree);
 extern bool lhd_warn_unused_global_decl (const_tree);
+extern void lhd_check_format_length (const_tree, const_tree, int, tree *);
 extern void lhd_incomplete_type_error (location_t, const_tree, const_tree);
 extern tree lhd_type_promotes_to (tree);
 extern void lhd_register_builtin_type (tree, const char *);
@@ -211,6 +212,7 @@ extern tree lhd_make_node (enum tree_code);
 #define LANG_HOOKS_FUNCTION_DECL_EXPLICIT_P hook_bool_tree_false
 #define LANG_HOOKS_FUNCTION_DECL_DELETED_P hook_bool_tree_false
 #define LANG_HOOKS_WARN_UNUSED_GLOBAL_DECL lhd_warn_unused_global_decl
+#define LANG_HOOKS_CHECK_FORMAT_LENGTH lhd_check_format_length
 #define LANG_HOOKS_POST_COMPILATION_PARSING_CLEANUPS NULL
 #define LANG_HOOKS_DECL_OK_FOR_SIBCALL	lhd_decl_ok_for_sibcall
 #define LANG_HOOKS_OMP_PRIVATIZE_BY_REFERENCE hook_bool_const_tree_false
@@ -236,6 +238,7 @@ extern tree lhd_make_node (enum tree_code);
   LANG_HOOKS_FUNCTION_PARM_EXPANDED_FROM_PACK_P, \
   LANG_HOOKS_GET_GENERIC_FUNCTION_DECL, \
   LANG_HOOKS_WARN_UNUSED_GLOBAL_DECL, \
+  LANG_HOOKS_CHECK_FORMAT_LENGTH, \
   LANG_HOOKS_POST_COMPILATION_PARSING_CLEANUPS, \
   LANG_HOOKS_DECL_OK_FOR_SIBCALL, \
   LANG_HOOKS_OMP_PRIVATIZE_BY_REFERENCE, \
diff --git a/gcc/langhooks.c b/gcc/langhooks.c
index 3256a9d..52e29f2 100644
--- a/gcc/langhooks.c
+++ b/gcc/langhooks.c
@@ -136,6 +136,14 @@ lhd_warn_unused_global_decl (const_tree decl)
   return true;
 }
 
+void
+lhd_check_format_length (const_tree ARG_UNUSED (fndecl),
+			 const_tree ARG_UNUSED (attrs),
+			 int ARG_UNUSED (nargs),
+			 tree * ARG_UNUSED (argarray))
+{
+}
+
 /* Set the DECL_ASSEMBLER_NAME for DECL.  */
 void
 lhd_set_decl_assembler_name (tree decl)
diff --git a/gcc/langhooks.h b/gcc/langhooks.h
index 0593424..d5c5257 100644
--- a/gcc/langhooks.h
+++ b/gcc/langhooks.h
@@ -201,6 +201,9 @@ struct lang_hooks_for_decls
      We will already have checked that it has static binding.  */
   bool (*warn_unused_global) (const_tree);
 
+  /* Check the length of output produced by sprintf-like functions.  */
+  void (*check_format_length) (const_tree, const_tree, int, tree *);
+
   /* Perform any post compilation-proper parser cleanups and
      processing.  This is currently only needed for the C++ parser,
      which hopefully can be cleaned up so this hook is no longer
diff --git a/gcc/passes.c b/gcc/passes.c
index 0565cfa..008799c 100644
--- a/gcc/passes.c
+++ b/gcc/passes.c
@@ -2425,8 +2425,15 @@ execute_pass_list_1 (opt_pass *pass)
 
       if (cfun == NULL)
 	return;
+
+      // inform (0, "executing pass: %s", pass->name);
+
       if (execute_one_pass (pass) && pass->sub)
-        execute_pass_list_1 (pass->sub);
+	{
+	  // inform (0, "executing subpass: %s", pass->sub->name);
+	  execute_pass_list_1 (pass->sub);
+	}
+
       pass = pass->next;
     }
   while (pass);
diff --git a/gcc/testsuite/gcc.dg/atomic/pr71675.c b/gcc/testsuite/gcc.dg/atomic/pr71675.c
new file mode 100644
index 0000000..0e344ac
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/atomic/pr71675.c
@@ -0,0 +1,32 @@
+/* PR c++/71675 - __atomic_compare_exchange_n returns wrong type for typed enum
+ */
+/* { dg-do compile { target c11 } } */
+
+#define Test(T)								\
+  do {									\
+    static T x;								\
+    int r [_Generic (__atomic_compare_exchange_n (&x, &x, x, 0, 0, 0),	\
+		     _Bool: 1, default: -1)];				\
+    (void)&r;								\
+  } while (0)
+
+void f (void)
+{
+  /* __atomic_compare_exchange_n would fail to return _Bool when
+     its arguments were one of the three character types. */
+  Test (char);
+  Test (signed char);
+  Test (unsigned char);
+
+  Test (int);
+  Test (unsigned int);
+
+  Test (long);
+  Test (unsigned long);
+
+  Test (long long);
+  Test (unsigned long long);
+
+  typedef enum E { e } E;
+  Test (E);
+}
diff --git a/gcc/testsuite/gcc.dg/format/c99-sprintf-length-1.c b/gcc/testsuite/gcc.dg/format/c99-sprintf-length-1.c
new file mode 100644
index 0000000..80b3455
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/format/c99-sprintf-length-1.c
@@ -0,0 +1,852 @@
+/* { dg-do compile } */
+/* { dg-options "-std=c99 -Wformat -Wformat-length=1 -ftrack-macro-expansion=0" } */
+
+/* When debugging, define LINE to the line number of the test case to exercise
+   and avoid exercising any of the others.  */
+#ifndef LINE
+# define LINE 0
+#endif
+
+typedef __SIZE_TYPE__ size_t;
+
+#if !__cplusplus
+typedef __WCHAR_TYPE__ wchar_t;
+#endif
+
+typedef unsigned char UChar;
+
+#define T(bufsize, fmt, ...)						\
+  __builtin___sprintf_chk (buffer, 0,					\
+      (!LINE || __LINE__ == LINE) ? bufsize : __SIZE_MAX__, fmt, __VA_ARGS__)
+
+char buffer [256];
+
+const char s0[] = "";
+const char s1[] = "1";
+const char s2[] = "12";
+const char s3[] = "123";
+const char s4[] = "1234";
+const char s5[] = "12345";
+const char s6[] = "123456";
+const char s7[] = "1234567";
+const char s8[] = "12345678";
+
+/* Exercise the "%c" and "%lc" directive.  */
+
+void test_sprintf_chk_c_const (void)
+{
+  /* Verify the full text of the diagnostic for just the distinct messages
+     and use abbreviations in subsequent test cases.  */
+  T (0, "%c",     0);            /* { dg-warning ".%c. directive writing .1. byte into a region of size .0." } */
+  T (1, "%c",     0);            /* { dg-warning "writing a terminating nul just past the end of a region of size .1." } */
+  T (1, "%c",   '1');            /* { dg-warning "nul just past the end" } */
+  T (2, "%c",   '1');
+  T (2, "%2c",  '1');            /* { dg-warning "nul just past the end" } */
+  T (2, "%3c",  '1');            /* { dg-warning "into a region" } */
+  T (2, "%c%c", '1', '2');       /* { dg-warning "nul just past the end" } */
+  T (3, "%c%c", '1', '2');
+
+  /* Wide characters.  */
+  T (0, "%lc",     0);           /* { dg-warning "nul past the end" } */
+  T (1, "%lc",     0);
+  T (1, "%lc%lc",  0, 0);
+  T (2, "%lc",     0);
+  T (2, "%lc%lc",  0, 0);
+
+  /* The following could result in as few as no bytes and in as many as
+     MB_CUR_MAX, but since the MB_CUR_MAX value is a runtime property
+     the write cannot be reliably diagnosed.  */
+  T (2, "%lc",  L'1');
+  T (2, "%1lc", L'1');
+  /* Writing some unknown number of bytes into a field two characters wide.  */
+  T (2, "%2lc", L'1');          /* { dg-warning "nul past the end" } */
+
+  T (3, "%lc%c",   L'1', '2');
+  /* Here in the best case each argument will format as single character,
+     causing the terminating NUL to be written just past the end.  */
+  T (3, "%lc%c%c", L'1', '2', '3');   /* { dg-warning "nul past the end" } */
+  T (3, "%lc%lc%c", L'1', L'2', '3'); /* { dg-warning "nul past the end" } */
+}
+
+/* Exercise the "%s" and "%ls" directive.  */
+
+void test_sprintf_chk_s_const (void)
+{
+  T (0, "%*s",  0, "");         /* { dg-warning "nul just past the end" } */
+  T (0, "%*s",  0, s0);         /* { dg-warning "nul just past the end" } */
+  T (1, "%*s",  0, "");
+  T (1, "%*s",  0, s0);
+  T (1, "%*s",  0, "\0");
+  T (1, "%*s",  0, "1");        /* { dg-warning "nul just past the end" } */
+  T (1, "%*s",  0, s1);         /* { dg-warning "nul just past the end" } */
+  T (1, "%1s",     "");         /* { dg-warning "nul just past the end" } */
+  T (1, "%1s",     s0);         /* { dg-warning "nul just past the end" } */
+  T (1, "%*s",  1, "");         /* { dg-warning "nul just past the end" } */
+  T (1, "%*s",  1, s0);         /* { dg-warning "nul just past the end" } */
+
+  T (1, "%.0s",    "123");
+  T (1, "%.0s",    s3);
+  T (1, "%.*s", 0, "123");
+  T (1, "%.*s", 0, s3);
+  T (1, "%.1s",    "123");      /* { dg-warning "nul just past the end" } */
+  T (1, "%.1s",    s3);         /* { dg-warning "nul just past the end" } */
+  T (1, "%.*s", 1, "123");      /* { dg-warning "nul just past the end" } */
+  T (1, "%.*s", 1, s3);         /* { dg-warning "nul just past the end" } */
+
+  T (2, "%*s",  0, "");
+  T (2, "%*s",  0, "1");
+  T (2, "%*s",  0, s1);
+  T (2, "%*s",  0, "1\0");
+  T (2, "%*s",  0, "12");       /* { dg-warning "nul just past the end" } */
+  T (2, "%*s",  0, s2);         /* { dg-warning "nul just past the end" } */
+
+  T (1, "%s%s", "", "");
+  T (1, "%s%s", s0, s0);
+  T (1, "%s%s", "", "1");       /* { dg-warning "nul just past the end" } */
+  T (1, "%s%s", s0, s1);        /* { dg-warning "nul just past the end" } */
+  T (1, "%s%s", "1", "");       /* { dg-warning "nul just past the end" } */
+  T (1, "%s%s", s1, s0);        /* { dg-warning "nul just past the end" } */
+  T (1, "%s%s", "1", "2");      /* { dg-warning "into a region" } */
+  T (1, "%s%s", s1, s1);        /* { dg-warning "into a region" } */
+
+  T (2, "%s%s", "", "");
+  T (2, "%s%s", "", "1");
+  T (2, "%s%s", "1", "");
+  T (2, "%s%s", "", "12");      /* { dg-warning "nul just past the end" } */
+  T (2, "%s%s", "1", "2");      /* { dg-warning "nul just past the end" } */
+  T (2, "%s%s", "12", "2");     /* { dg-warning "into a region" } */
+  T (2, "%s%s", "1", "23");     /* { dg-warning "into a region" } */
+  T (2, "%s%s", "12", "3");     /* { dg-warning "into a region" } */
+  T (2, "%s%s", "12", "34");    /* { dg-warning "into a region" } */
+
+  T (2, "_%s",   "");
+  T (2, "%%%s",  "");
+  T (2, "%s%%",  "");
+  T (2, "_%s",   "1");          /* { dg-warning "nul just past the end" } */
+  T (2, "%%%s",  "1");          /* { dg-warning "nul just past the end" } */
+  T (2, "%s%%",  "1");          /* { dg-warning "nul just past the end" } */
+  T (2, "_%s",   "12");         /* { dg-warning "into a region" } */
+  T (2, "__%s",  "1");          /* { dg-warning "into a region" } */
+
+  T (3, "__%s", "");
+  T (3, "__%s", "1");           /* { dg-warning "nul just past the end" } */
+  T (3, "%s_%s", "", "");
+  T (3, "%s_%s", "1", "");
+  T (3, "%s_%s", "", "1");
+  T (3, "%s_%s", "1", "2");     /* { dg-warning "nul just past the end" } */
+
+  /* Wide strings.  */
+  T (0, "%ls",      L"");       /* { dg-warning "nul past the end" } */
+  T (1, "%ls",      L"");
+  T (1, "%ls",      L"\0");
+  T (1, "%1ls",     L"");       /* { dg-warning "nul past the end" } */
+
+  T (0, "%*ls",  0, L"");       /* { dg-warning "nul past the end" } */
+  T (1, "%*ls",  0, L"");
+  T (1, "%*ls",  0, L"\0");
+  T (1, "%*ls",  1, L"");       /* { dg-warning "nul past the end" } */
+
+  T (1, "%ls",      L"1");      /* { dg-warning "nul past the end" } */
+  T (1, "%.0ls",    L"1");
+  T (2, "%.0ls",    L"1");
+  T (2, "%.1ls",    L"1");
+  T (2, "%.2ls",    L"1");      /* { dg-warning "may write a terminating nul past the end" } */
+
+  T (3, "%.0ls",    L"1");
+  T (3, "%.1ls",    L"1");
+  T (3, "%.2ls",    L"1");
+}
+
+/* Exercise the "%hhd", "%hhi", "%hho", "%hhu", and "%hhx" directives.  */
+
+void test_sprintf_chk_hh_const (void)
+{
+  T (1, "%hhd",         0);     /* { dg-warning "nul just past the end" } */
+  T (1, "%hhd",         1);     /* { dg-warning "nul just past the end" } */
+  T (1, "%hhd",        -1);     /* { dg-warning "into a region" } */
+  T (1, "%+hhd",        0);     /* { dg-warning "into a region" } */
+  T (1, "%+hhd",        1);     /* { dg-warning "into a region" } */
+  T (1, "%-hhd",        0);     /* { dg-warning "nul just past the end" } */
+
+  T (1, "%hhi",         0);     /* { dg-warning "nul just past the end" } */
+  T (1, "%hhi",         1);     /* { dg-warning "nul just past the end" } */
+  T (1, "%hhi",        -1);     /* { dg-warning "into a region" } */
+  T (1, "%+hhi",        0);     /* { dg-warning "into a region" } */
+  T (1, "%+hhi",        1);     /* { dg-warning "into a region" } */
+  T (1, "%-hhi",        0);     /* { dg-warning "nul just past the end" } */
+
+  T (2, "%hhi",         0);
+  T (2, "%hhi",         1);
+  T (2, "%hhi",         9);
+  T (2, "% hhi",        9);     /* { dg-warning "nul just past the end" } */
+  T (2, "%+hhi",        9);     /* { dg-warning "nul just past the end" } */
+  T (2, "%-hhi",        9);
+  T (2, "%hhi",        10);     /* { dg-warning "nul just past the end" } */
+  T (2, "%hhi",        -1);     /* { dg-warning "nul just past the end" } */
+  T (2, "% hhi",       -1);     /* { dg-warning "nul just past the end" } */
+  T (2, "%+hhi",       -1);     /* { dg-warning "nul just past the end" } */
+  T (2, "%-hhi",       -1);     /* { dg-warning "nul just past the end" } */
+
+  T (2, "%hho",         0);
+  T (2, "%hho",         1);
+  T (2, "%hho",         7);
+  T (2, "%hho",       010);     /* { dg-warning "nul just past the end" } */
+  T (2, "%hho",       077);     /* { dg-warning "nul just past the end" } */
+  T (2, "%hho",        -1);     /* { dg-warning "into a region" } */
+
+  T (2, "%hhx",         0);
+  T (2, "%hhX",         1);
+  T (2, "%hhx",         7);
+  T (2, "%hhX",         8);
+  T (2, "%hhx",        -1);     /* { dg-warning "nul just past the end" } */
+  T (2, "%hhX",       0xf);
+  T (2, "%hhx",      0x10);     /* { dg-warning "nul just past the end" } */
+  T (2, "%hhX",      0xff);     /* { dg-warning "nul just past the end" } */
+
+  T (1, "%#hhx",        0);     /* { dg-warning "nul just past the end" } */
+  T (2, "%#hhx",        0);
+  T (3, "%#hhx",        1);     /* { dg-warning "nul just past the end" } */
+
+  T (4, "%hhd",       255);
+  T (4, "%hhd",       256);
+  T (4, "%hhd",     0xfff);
+  T (4, "%hhd",    0xffff);
+
+  T (4, "%hhi",       255);
+  T (4, "%hhi",       256);
+  T (4, "%hhi",     0xfff);
+  T (4, "%hhi",    0xffff);
+
+  T (4, "%hhu",        -1);
+  T (4, "%hhu",       255);
+  T (4, "%hhu",       256);
+  T (4, "%hhu",     0xfff);
+  T (4, "%hhu",    0xffff);
+
+  T (4, "%#hhx",        0);
+  T (4, "%#hhx",        1);
+  T (4, "%#hhx",       -1);     /* { dg-warning "nul just past the end" } */
+  T (4, "%#hhx",      0xf);
+  T (4, "%#hhx",     0x10);     /* { dg-warning "nul just past the end" } */
+  T (4, "%#hhx",     0xff);     /* { dg-warning "nul just past the end" } */
+  T (4, "%#hhx",    0xfff);     /* { dg-warning "nul just past the end" } */
+
+  T (4, "%hhi %hhi",  0,  0);
+  T (4, "%hhi %hhi",  9,  9);
+  T (4, "%hhi %hhi",  1, 10);   /* { dg-warning "nul just past the end" } */
+  T (4, "%hhi %hhi", 10,  1);   /* { dg-warning "nul just past the end" } */
+  T (4, "%hhi %hhi", 11, 12);   /* { dg-warning "into a region" } */
+
+  /* FIXME: Move the boundary test cases into a file of their own that's
+     exercised only on targets with the matching type limits (otherwise
+     they'll fail).  */
+#undef MAX
+#define MAX   127
+
+#undef MIN
+#define MIN   (-MAX -1)
+
+  T (1, "%hhi",        MAX);    /* { dg-warning "into a region" } */
+  T (1, "%hhi",        MIN);    /* { dg-warning "into a region" } */
+  T (1, "%hhi",  MAX +   1);    /* { dg-warning "into a region" } */
+
+  T (2, "%hhi",  MAX +   1);    /* { dg-warning "into a region" } */
+  T (2, "%hhi",  MAX +  10);    /* { dg-warning "into a region" } */
+  T (2, "%hhi",  MAX + 100);    /* { dg-warning "into a region" } */
+}
+
+void test_sprintf_chk_h_const (void)
+{
+  T (1, "%hu",          0);     /* { dg-warning "nul just past the end" } */
+  T (1, "%hu",          1);     /* { dg-warning "nul just past the end" } */
+  T (1, "%hu",         -1);     /* { dg-warning "into a region" } */
+
+  T (2, "%hi",          0);
+  T (2, "%hi",          1);
+  T (2, "%hi",          9);
+  T (2, "% hi",         9);     /* { dg-warning "nul just past the end" } */
+  T (2, "%+hi",         9);     /* { dg-warning "nul just past the end" } */
+  T (2, "%-hi",         9);
+  T (2, "%hi",         10);     /* { dg-warning "nul just past the end" } */
+  T (2, "%hi",         -1);     /* { dg-warning "nul just past the end" } */
+  T (2, "% hi",        -2);     /* { dg-warning "nul just past the end" } */
+  T (2, "%+hi",        -3);     /* { dg-warning "nul just past the end" } */
+  T (2, "%-hi",        -4);     /* { dg-warning "nul just past the end" } */
+
+  T (2, "%hu",          0);
+  T (2, "%hu",          1);
+  T (2, "%hu",          9);
+  T (2, "%hu",         10);     /* { dg-warning "nul just past the end" } */
+  T (2, "%hu",         -1);     /* { dg-warning "into a region" } */
+
+  T (2, "%ho",          0);
+  T (2, "%ho",          1);
+  T (2, "%ho",          7);
+  T (2, "%ho",        010);     /* { dg-warning "nul just past the end" } */
+  T (2, "%ho",        077);     /* { dg-warning "nul just past the end" } */
+  T (2, "%ho",       0100);     /* { dg-warning "into a region" } */
+  T (2, "%ho",         -1);     /* { dg-warning "into a region" } */
+
+  T (2, "%hx",          0);
+  T (2, "%hx",          1);
+  T (2, "%hx",          7);
+  T (2, "%hx",        0xf);
+  T (2, "%hx",       0x10);     /* { dg-warning "nul just past the end" } */
+  T (2, "%hx",       0xff);     /* { dg-warning "nul just past the end" } */
+  T (2, "%hx",      0x100);     /* { dg-warning "into a region" } */
+  T (2, "%hx",         -1);     /* { dg-warning "into a region" } */
+
+  T (3, "% hi",         7);
+  T (3, "%+hi",         8);
+  T (3, "%-hi",         9);
+  T (3, "%hi",         10);
+  T (3, "%hi",         -1);
+  T (3, "% hi",        -2);
+  T (3, "%+hi",        -3);
+  T (3, "%-hi",        -4);
+
+  T (5, "%hu",       9999);
+  T (5, "%hu",      10000);     /* { dg-warning "nul just past the end" } */
+  T (5, "%hu",      65535);     /* { dg-warning "nul just past the end" } */
+
+  T (1, "%#hx",         0);     /* { dg-warning "nul just past the end" } */
+  T (2, "%#hx",         0);
+  T (3, "%#hx",         1);     /* { dg-warning "nul just past the end" } */
+
+  T (4, "%#hx",         0);
+  T (4, "%#hx",         1);
+  T (4, "%#hx",       0xf);
+  T (4, "%#hx",      0x10);     /* { dg-warning "nul just past the end" } */
+  T (4, "%#hx",      0xff);     /* { dg-warning "nul just past the end" } */
+  T (4, "%#hx",     0x100);     /* { dg-warning "into a region" } */
+  T (4, "%#hx",        -1);     /* { dg-warning "into a region" } */
+
+#undef MAX
+#define MAX   65535
+
+  T (1, "%hhu",         0);     /* { dg-warning "nul just past the end" } */
+  T (1, "%hhu",         1);     /* { dg-warning "nul just past the end" } */
+  T (1, "%hhu",        -1);     /* { dg-warning "into a region" } */
+  T (1, "%hhu",       MAX);     /* { dg-warning "into a region" } */
+  T (1, "%hhu",  MAX +  1);     /* { dg-warning "nul just past the end" } */
+}
+
+void test_sprintf_chk_integer_const (void)
+{
+  T ( 1, "%i",          0);         /* { dg-warning "nul just past the end" } */
+  T ( 1, "%i",          1);         /* { dg-warning "nul just past the end" } */
+  T ( 1, "%i",         -1);         /* { dg-warning "into a region" } */
+  T ( 1, "%i_",         1);         /* { dg-warning "character ._. at offset .2. just past the end" } */
+  T ( 1, "_%i",         1);         /* { dg-warning "into a region" } */
+  T ( 1, "_%i_",        1);         /* { dg-warning "into a region" } */
+  T ( 1, "%o",          0);         /* { dg-warning "nul just past the end" } */
+  T ( 1, "%u",          0);         /* { dg-warning "nul just past the end" } */
+  T ( 1, "%x",          0);         /* { dg-warning "nul just past the end" } */
+  T ( 1, "%#x",         0);         /* { dg-warning "nul just past the end" } */
+  T ( 1, "%x",          1);         /* { dg-warning "nul just past the end" } */
+  T ( 1, "%#x",         1);         /* { dg-warning "into a region" } */
+
+  T ( 2, "%i",          0);
+  T ( 2, "%i",          1);
+  T ( 2, "%i",          9);
+  T ( 2, "%i",         -1);         /* { dg-warning "nul just past the end" } */
+  T ( 2, "%i",         10);         /* { dg-warning "nul just past the end" } */
+  T ( 2, "%i_",         0);         /* { dg-warning "nul just past the end" } */
+  T ( 2, "_%i",         0);         /* { dg-warning "nul just past the end" } */
+  T ( 2, "_%i_",        0);         /* { dg-warning "character ._. at offset .3. just past the end" } */
+  T ( 2, "%o",          1);
+  T ( 2, "%o",          7);
+  T ( 2, "%o",        010);         /* { dg-warning "nul just past the end" } */
+  T ( 2, "%o",       0100);         /* { dg-warning "into a region" } */
+  T ( 2, "%x",          1);
+  T ( 2, "%#x",         1);         /* { dg-warning "into a region" } */
+  T ( 2, "%x",        0xa);
+  T ( 2, "%x",        0xf);
+  T ( 2, "%x",       0x10);         /* { dg-warning "nul just past the end" } */
+  T ( 2, "%x",       0xff);         /* { dg-warning "nul just past the end" } */
+  T ( 2, "%x",      0x1ff);         /* { dg-warning "into a region" } */
+
+  T ( 3, "%i",          0);
+  T ( 3, "%i",          1);
+  T ( 3, "%i",          9);
+  T ( 3, "%i",         -9);
+  T ( 3, "%i",         10);
+  T ( 3, "%i",         99);
+  T ( 3, "%i",        -99);         /* { dg-warning "nul just past the end" } */
+
+  /* ~0U is formatted into exactly three bytes as "-1" followed by
+     the terminating NUL character.  */
+  T ( 3, "%+i",       ~0U);
+  T ( 3, "%-i",       ~0U);
+  T ( 3, "% i",       ~0U);
+
+  T ( 8, "%8u",         1);         /* { dg-warning "nul just past the end" } */
+  T ( 9, "%8u",         1);
+
+#undef MAX
+#define MAX   2147483647   /* 10 digits. */
+#undef MIN
+#define MIN   (-MAX -1)    /* Sign plus 10 digits. */
+
+  T ( 1, "%i",        MAX);         /* { dg-warning "into a region" } */
+  T ( 1, "%i",        MIN);         /* { dg-warning "into a region" } */
+  T ( 2, "%i",        MAX);         /* { dg-warning "into a region" } */
+  T ( 2, "%i",        MIN);         /* { dg-warning "into a region" } */
+  T (10, "%i",  123456789);
+  T (10, "%i", -123456789);         /* { dg-warning "nul just past the end" } */
+  T (10, "%i",        MAX);         /* { dg-warning "nul just past the end" } */
+  T (10, "%i",        MIN);         /* { dg-warning "into a region" } */
+
+  T (11, "%i",        MAX);
+  T (11, "%i",        MIN);         /* { dg-warning "nul just past the end" } */
+}
+
+void test_sprintf_chk_l_const (void)
+{
+  T ( 1, "%li",        0L);         /* { dg-warning "nul just past the end" } */
+  T ( 1, "%li",        1L);         /* { dg-warning "nul just past the end" } */
+  T ( 1, "%li",       -1L);         /* { dg-warning "into a region" } */
+  T ( 1, "%li_",       1L);         /* { dg-warning "character ._. at offset .3. just past the end" } */
+  T ( 1, "_%li",       1L);         /* { dg-warning "into a region" } */
+  T ( 1, "_%li_",      1L);         /* { dg-warning "into a region" } */
+}
+
+void test_sprintf_chk_z_const (void)
+{
+  T ( 1, "%zi",        (size_t)0);  /* { dg-warning "nul just past the end" } */
+  T ( 1, "%zi",        (size_t)1);  /* { dg-warning "nul just past the end" } */
+  T ( 1, "%zi",        (size_t)-1L);/* { dg-warning "into a region" } */
+  T ( 1, "%zi_",       (size_t)1);  /* { dg-warning "character ._. at offset .3. just past the end" } */
+  T ( 1, "_%zi",       (size_t)1);  /* { dg-warning "into a region" } */
+  T ( 1, "_%zi_",      (size_t)1);  /* { dg-warning "into a region" } */
+
+  T ( 2, "%zu",        (size_t)1);
+  T ( 2, "%zu",        (size_t)9);
+  T ( 2, "%zu",        (size_t)10); /* { dg-warning "nul just past the end" } */
+}
+
+void test_sprintf_chk_e_const (void)
+{
+  T  (0, "%E",   0.0);           /* { dg-warning "into a region" } */
+  T  (0, "%e",   0.0);           /* { dg-warning "into a region" } */
+  T ( 1, "%E",   1.0);           /* { dg-warning "into a region" } */
+  T ( 1, "%e",   1.0);           /* { dg-warning "into a region" } */
+  T ( 2, "%e",   2.0);           /* { dg-warning "into a region" } */
+  T ( 3, "%e",   3.0);           /* { dg-warning "into a region" } */
+  T (12, "%e",   1.2);           /* { dg-warning "nul just past the end" } */
+  T (12, "%e",  12.0);           /* { dg-warning "nul just past the end" } */
+  T (13, "%e",   1.3);
+  T (13, "%E",  13.0);
+  T (13, "%e",  13.0);
+
+  T ( 5, "%.0e", 0.0);           /* { dg-warning "nul just past the end" } */
+  T ( 5, "%.0e", 1.0);           /* { dg-warning "nul just past the end" } */
+  T ( 6, "%.0e", 1.0);
+}
+
+/* At -Wformat-length level 1 unknown numbers are assumed to have
+   the value one, and unknown strings are assumed to have a zero
+   length.  */
+
+void test_sprintf_chk_s_nonconst (const char *s)
+{
+  T (0, "%s",   s);             /* { dg-warning "nul past the end" } */
+  T (1, "%s",   s);
+  /* The following will definitely write past the end of the buffer,
+     but since at level 1 the length of an unknown string argument
+     is assumed to be zero, it will write the terminating nul past
+     the end (we don't print "just past the end" when we're not
+     sure which we can't be with an unknown string.  */
+  T (1, "%1s",  s);             /* { dg-warning "writing a terminating nul past the end" } */
+}
+
+/* Exercise the hh length modifier with all integer specifiers and
+   a non-constant argument.  */
+
+void test_sprintf_chk_hh_nonconst (int a)
+{
+  T (0, "%hhd",         a);     /* { dg-warning "into a region" } */
+  T (0, "%hhi",         a);     /* { dg-warning "into a region" } */
+  T (0, "%hhu",         a);     /* { dg-warning "into a region" } */
+  T (0, "%hhx",         a);     /* { dg-warning "into a region" } */
+
+  T (1, "%hhd",         a);     /* { dg-warning "nul past the end" } */
+  T (1, "%hhi",         a);     /* { dg-warning "nul past the end" } */
+  T (1, "%hhu",         a);     /* { dg-warning "nul past the end" } */
+  T (1, "%hhx",         a);     /* { dg-warning "nul past the end" } */
+
+  T (1, "% hhd",        a);     /* { dg-warning "into a region" } */
+  T (1, "% hhi",        a);     /* { dg-warning "into a region" } */
+  T (1, "%+hhd",        a);     /* { dg-warning "into a region" } */
+  T (1, "%+hhi",        a);     /* { dg-warning "into a region" } */
+  T (1, "%-hhd",        a);     /* { dg-warning "nul past the end" } */
+  T (1, "%-hhi",        a);     /* { dg-warning "nul past the end" } */
+
+  T (2, "%hhd",         a);
+  T (2, "%hhi",         a);
+  T (2, "%hho",         a);
+  T (2, "%hhu",         a);
+  T (2, "%hhx",         a);
+
+  T (2, "% hhd",        a);     /* { dg-warning "nul past the end" } */
+  T (2, "% hhi",        a);     /* { dg-warning "nul past the end" } */
+  T (2, "% hho",        a);     /* { dg-warning ". . flag used with .%o." } */
+  T (2, "% hhu",        a);     /* { dg-warning ". . flag used with .%u." } */
+  T (2, "% hhx",        a);     /* { dg-warning ". . flag used with .%x." } */
+
+  T (2, "#%hho",        a);     /* { dg-warning "nul past the end" } */
+  T (2, "#%hhx",        a);     /* { dg-warning "nul past the end" } */
+
+  T (3, "%2hhd",        a);
+  T (3, "%2hhi",        a);
+  T (3, "%2hho",        a);
+  T (3, "%2hhu",        a);
+  T (3, "%2hhx",        a);
+}
+
+/* Exercise the h length modifier with all integer specifiers and
+   a non-constant argument.  */
+
+void test_sprintf_chk_h_nonconst (int a)
+{
+  T (0, "%hd",          a);     /* { dg-warning "into a region" } */
+  T (0, "%hi",          a);     /* { dg-warning "into a region" } */
+  T (0, "%hu",          a);     /* { dg-warning "into a region" } */
+  T (0, "%hx",          a);     /* { dg-warning "into a region" } */
+
+  T (1, "%hd",          a);     /* { dg-warning "nul past the end" } */
+  T (1, "%hi",          a);     /* { dg-warning "nul past the end" } */
+  T (1, "%hu",          a);     /* { dg-warning "nul past the end" } */
+  T (1, "%hx",          a);     /* { dg-warning "nul past the end" } */
+
+  T (1, "% hd",         a);     /* { dg-warning "into a region" } */
+  T (1, "% hi",         a);     /* { dg-warning "into a region" } */
+  T (1, "%+hd",         a);     /* { dg-warning "into a region" } */
+  T (1, "%+hi",         a);     /* { dg-warning "into a region" } */
+  T (1, "%-hd",         a);     /* { dg-warning "nul past the end" } */
+  T (1, "%-hi",         a);     /* { dg-warning "nul past the end" } */
+
+  T (2, "%hd",          a);
+  T (2, "%hi",          a);
+  T (2, "%ho",          a);
+  T (2, "%hu",          a);
+  T (2, "%hx",          a);
+
+  T (2, "% hd",         a);     /* { dg-warning "nul past the end" } */
+  T (2, "% hi",         a);     /* { dg-warning "nul past the end" } */
+  T (2, "% ho",         a);     /* { dg-warning ". . flag used with .%o." } */
+  T (2, "% hu",         a);     /* { dg-warning ". . flag used with .%u." } */
+  T (2, "% hx",         a);     /* { dg-warning ". . flag used with .%x." } */
+
+  T (2, "#%ho",         a);     /* { dg-warning "nul past the end" } */
+  T (2, "#%hx",         a);     /* { dg-warning "nul past the end" } */
+
+  T (3, "%2hd",         a);
+  T (3, "%2hi",         a);
+  T (3, "%2ho",         a);
+  T (3, "%2hu",         a);
+  T (3, "%2hx",         a);
+}
+
+/* Exercise all integer specifiers with no modifier and a non-constant
+   argument.  */
+
+void test_sprintf_chk_int_nonconst (int a)
+{
+  T (0, "%d",           a);     /* { dg-warning "into a region" } */
+  T (0, "%i",           a);     /* { dg-warning "into a region" } */
+  T (0, "%u",           a);     /* { dg-warning "into a region" } */
+  T (0, "%x",           a);     /* { dg-warning "into a region" } */
+
+  T (1, "%d",           a);     /* { dg-warning "nul past the end" } */
+  T (1, "%i",           a);     /* { dg-warning "nul past the end" } */
+  T (1, "%u",           a);     /* { dg-warning "nul past the end" } */
+  T (1, "%x",           a);     /* { dg-warning "nul past the end" } */
+
+  T (1, "% d",          a);     /* { dg-warning "into a region" } */
+  T (1, "% i",          a);     /* { dg-warning "into a region" } */
+  T (1, "%+d",          a);     /* { dg-warning "into a region" } */
+  T (1, "%+i",          a);     /* { dg-warning "into a region" } */
+  T (1, "%-d",          a);     /* { dg-warning "nul past the end" } */
+  T (1, "%-i",          a);     /* { dg-warning "nul past the end" } */
+
+  T (2, "%d",           a);
+  T (2, "%i",           a);
+  T (2, "%o",           a);
+  T (2, "%u",           a);
+  T (2, "%x",           a);
+
+  T (2, "% d",          a);     /* { dg-warning "nul past the end" } */
+  T (2, "% i",          a);     /* { dg-warning "nul past the end" } */
+  T (2, "% o",          a);     /* { dg-warning ". . flag used with .%o." } */
+  T (2, "% u",          a);     /* { dg-warning ". . flag used with .%u." } */
+  T (2, "% x",          a);     /* { dg-warning ". . flag used with .%x." } */
+
+  T (2, "#%o",          a);     /* { dg-warning "nul past the end" } */
+  T (2, "#%x",          a);     /* { dg-warning "nul past the end" } */
+
+  T (3, "%2d",          a);
+  T (3, "%2i",          a);
+  T (3, "%2o",          a);
+  T (3, "%2u",          a);
+  T (3, "%2x",          a);
+}
+
+void test_sprintf_chk_e_nonconst (double d)
+{
+  T  (0, "%E",          d);           /* { dg-warning "writing at least .13. bytes into a region of size .0." } */
+  T  (0, "%e",          d);           /* { dg-warning "into a region" } */
+  T ( 1, "%E",          d);           /* { dg-warning "into a region" } */
+  T ( 1, "%e",          d);           /* { dg-warning "into a region" } */
+  T ( 2, "%e",          d);           /* { dg-warning "into a region" } */
+  T ( 3, "%e",          d);           /* { dg-warning "into a region" } */
+  T (12, "%e",          d);           /* { dg-warning "into a region" } */
+  T (12, "%e",          d);           /* { dg-warning "into a region" } */
+  T (13, "%E",          d);           /* { dg-warning "nul past the end" } */
+  T (13, "%e",          d);           /* { dg-warning "nul past the end" } */
+  T (14, "%E",          d);
+  T (14, "%e",          d);
+
+  T ( 5, "%.0e",        d);           /* { dg-warning "directive writing at least .6. bytes into a region of size .5." } */
+  T ( 6, "%.0e",        d);           /* { dg-warning "nul past the end" } */
+  T ( 7, "%.0e",        d);
+}
+
+void test_sprintf_chk_f_nonconst (double d)
+{
+  T  (0, "%F",          d);           /* { dg-warning "into a region" } */
+  T  (0, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 1, "%F",          d);           /* { dg-warning "into a region" } */
+  T ( 1, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 2, "%F",          d);           /* { dg-warning "into a region" } */
+  T ( 2, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 3, "%F",          d);           /* { dg-warning "into a region" } */
+  T ( 3, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 4, "%F",          d);           /* { dg-warning "into a region" } */
+  T ( 4, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 5, "%F",          d);           /* { dg-warning "into a region" } */
+  T ( 5, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 6, "%F",          d);           /* { dg-warning "into a region" } */
+  T ( 6, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 7, "%F",          d);           /* { dg-warning "into a region" } */
+  T ( 7, "%f",          d);           /* { dg-warning "into a region" } */
+  T ( 8, "%F",          d);           /* { dg-warning "nul past the end" } */
+  T ( 8, "%f",          d);           /* { dg-warning "nul past the end" } */
+  T ( 9, "%F",          d);
+  T ( 9, "%f",          d);
+}
+
+/* Tests for __builtin_vsprintf_chk are the same as those for
+   __builtin_sprintf_chk with non-constant arguments.  */
+#undef T
+#define T(bufsize, fmt)							\
+  __builtin___vsprintf_chk (buffer, 0,					\
+      (!LINE || __LINE__ == LINE) ? bufsize : __SIZE_MAX__, fmt, va)
+
+void test_vsprintf_chk_c (__builtin_va_list va)
+{
+  /* Verify the full text of the diagnostic for just the distinct messages
+     and use abbreviations in subsequent test cases.  */
+  T (0, "%c");              /* { dg-warning ".%c. directive writing .1. byte into a region of size .0." } */
+  T (1, "%c");              /* { dg-warning "writing a terminating nul just past the end of a region of size .1." } */
+  T (1, "%c");              /* { dg-warning "nul just past the end" } */
+  T (2, "%c");
+  T (2, "%2c");             /* { dg-warning "nul just past the end" } */
+  T (2, "%3c");             /* { dg-warning "into a region" } */
+  T (2, "%c%c");            /* { dg-warning "nul just past the end" } */
+  T (3, "%c%c");
+
+  /* Wide characters.  */
+  T (0, "%lc");             /* { dg-warning "nul past the end" } */
+  T (1, "%lc");
+  T (2, "%lc");
+
+  /* The following could result in as few as a single byte and in as many
+     as MB_CUR_MAX, but since the MB_CUR_MAX value is a runtime property
+     the write cannot be reliably diagnosed.  */
+  T (2, "%lc");
+  T (2, "%1lc");
+  /* Writing some unknown number of bytes into a field two characters wide.  */
+  T (2, "%2lc");            /* { dg-warning "nul past the end" } */
+  T (2, "%lc%lc");
+
+  T (3, "%lc%c");
+  /* Here in the best case each argument will format as single character,
+     causing the terminating NUL to be written just past the end.  */
+  T (3, "%lc%c%c");
+
+}
+
+void test_vsprintf_chk_int (__builtin_va_list va)
+{
+  T (0, "%d");                /* { dg-warning "into a region" } */
+  T (0, "%i");                /* { dg-warning "into a region" } */
+  T (0, "%u");                /* { dg-warning "into a region" } */
+  T (0, "%x");                /* { dg-warning "into a region" } */
+
+  T (1, "%d");                /* { dg-warning "nul past the end" } */
+  T (1, "%i");                /* { dg-warning "nul past the end" } */
+  T (1, "%u");                /* { dg-warning "nul past the end" } */
+  T (1, "%x");                /* { dg-warning "nul past the end" } */
+
+  T (1, "% d");               /* { dg-warning "into a region" } */
+  T (1, "% i");               /* { dg-warning "into a region" } */
+  T (1, "%+d");               /* { dg-warning "into a region" } */
+  T (1, "%+i");               /* { dg-warning "into a region" } */
+  T (1, "%-d");               /* { dg-warning "nul past the end" } */
+  T (1, "%-i");               /* { dg-warning "nul past the end" } */
+
+  T (2, "%d");
+  T (2, "%i");
+  T (2, "%o");
+  T (2, "%u");
+  T (2, "%x");
+
+  T (2, "% d");               /* { dg-warning "nul past the end" } */
+  T (2, "% i");               /* { dg-warning "nul past the end" } */
+  T (2, "% o");               /* { dg-warning ". . flag used with .%o." } */
+  T (2, "% u");               /* { dg-warning ". . flag used with .%u." } */
+  T (2, "% x");               /* { dg-warning ". . flag used with .%x." } */
+
+  T (2, "#%o");               /* { dg-warning "nul past the end" } */
+  T (2, "#%x");               /* { dg-warning "nul past the end" } */
+
+  T (3, "%2d");
+  T (3, "%2i");
+  T (3, "%2o");
+  T (3, "%2u");
+  T (3, "%2x");
+}
+
+#undef T
+#define T(bufsize, fmt, ...)						\
+  __builtin_snprintf (buffer,						\
+      (!LINE || __LINE__ == LINE) ? bufsize : __SIZE_MAX__, fmt, __VA_ARGS__)
+
+void test_snprintf_c_const (void)
+{
+  /* Verify the full text of the diagnostic for just the distinct messages
+     and use abbreviations in subsequent test cases.  */
+
+  /* A call to snprintf with a buffer of zero size is a request to determine
+     the size of output without writing anything into the destination. No
+     warning must be issued.  */
+  T (0, "%c",     0);
+  T (1, "%c",     0);            /* { dg-warning "output truncated while copying format string into a region of size .1." } */
+  T (1, "%c",   '1');            /* { dg-warning "output truncated" } */
+  T (2, "%c",   '1');
+  T (2, "%2c",  '1');            /* { dg-warning "output truncated" } */
+  T (2, "%3c",  '1');            /* { dg-warning "directive output truncated" } */
+  T (2, "%c%c", '1', '2');       /* { dg-warning "output truncated while copying format string" } */
+  T (3, "%c%c", '1', '2');
+
+  /* Wide characters.  */
+  T (0, "%lc",  0);
+  T (1, "%lc",  0);
+  T (2, "%lc",  0);
+
+  /* The following could result in as few as a single byte and in as many
+     as MB_CUR_MAX, but since the MB_CUR_MAX value is a runtime property
+     the write cannot be reliably diagnosed.  */
+  T (2, "%lc",  L'1');
+  T (2, "%1lc", L'1');
+  /* Writing at least 1 characted into a field two characters wide.  */
+  T (2, "%2lc", L'1');          /* { dg-warning "output truncated while copying format string" } */
+
+  T (3, "%lc%c",   L'1', '2');
+  /* Here in the best case each argument will format as single character,
+     causing the output to be truncated just before the terminating NUL
+     (i.e., cutting off the '3').  */
+  T (3, "%lc%c%c", L'1', '2', '3');   /* { dg-warning "output truncated" } */
+  T (3, "%lc%lc%c", L'1', L'2', '3'); /* { dg-warning "output truncated" } */
+}
+
+#undef T
+#define T(bufsize, fmt, ...)						\
+  __builtin___snprintf_chk (buffer, bufsize, 0,				\
+      (!LINE || __LINE__ == LINE) ? bufsize : __SIZE_MAX__, fmt, __VA_ARGS__)
+
+void test_snprintf_chk_c_const (void)
+{
+  /* Verify that specifying a size of the destination buffer that's
+     bigger than its actual size (normally determined and passed to
+     the function by __builtin_object_size) is diagnosed.  */
+  __builtin___snprintf_chk (buffer, 3, 0, 2, " ");   /* { dg-warning "destination size .3. exceeds the size of the object .2." } */
+
+  T (0, "%c",     0);
+  T (0, "%c%c",   0, 0);
+  T (0, "%c_%c",  0, 0);
+  T (0, "_%c_%c", 0, 0);
+
+  T (1, "%c",     0);            /* { dg-warning "output truncated while copying format string into a region of size .1." } */
+  T (1, "%c",   '1');            /* { dg-warning "output truncated" } */
+  T (2, "%c",   '1');
+  T (2, "%2c",  '1');            /* { dg-warning "output truncated" } */
+  T (2, "%3c",  '1');            /* { dg-warning "directive output truncated" } */
+  T (2, "%c%c", '1', '2');       /* { dg-warning "output truncated while copying format string" } */
+  T (3, "%c%c", '1', '2');
+  T (3, "%c_%c", '1', '2');      /* { dg-warning "output truncated" } */
+
+  /* Wide characters.  */
+  T (0, "%lc",  0);
+  T (1, "%lc",  0);
+  T (2, "%lc",  0);
+
+  /* The following could result in as few as a single byte and in as many
+     as MB_CUR_MAX, but since the MB_CUR_MAX value is a runtime property
+     the write cannot be reliably diagnosed.  */
+  T (2, "%lc",  L'1');
+  T (2, "%1lc", L'1');
+  /* Writing at least 1 characted into a field two characters wide.  */
+  T (2, "%2lc", L'1');          /* { dg-warning "output truncated while copying format string" } */
+
+  T (3, "%lc%c",   L'1', '2');
+  /* Here in the best case each argument will format as single character,
+     causing the output to be truncated just before the terminating NUL
+     (i.e., cutting off the '3').  */
+  T (3, "%lc%c%c", L'1', '2', '3');   /* { dg-warning "output truncated" } */
+  T (3, "%lc%lc%c", L'1', L'2', '3'); /* { dg-warning "output truncated" } */
+}
+
+#undef T
+#define T(bufsize, fmt)							\
+  __builtin_vsnprintf (buffer,						\
+      (!LINE || __LINE__ == LINE) ? bufsize : __SIZE_MAX__, fmt, va)
+
+void test_vsnprintf_s (__builtin_va_list va)
+{
+  T (0, "%s");
+  T (1, "%s");
+  T (1, "%1s");             /* { dg-warning "output truncated while copying format string" } */
+
+  T (2, "%s%s");
+  T (2, "%s%s_");
+  T (2, "%s_%s");
+  T (2, "_%s%s");
+  T (2, "_%s_%s");          /* { dg-warning "output truncated while copying format string" } */
+}
+
+#undef T
+#define T(bufsize, fmt)							\
+  __builtin___vsnprintf_chk (buffer, bufsize, 0,			\
+      (!LINE || __LINE__ == LINE) ? bufsize : __SIZE_MAX__, fmt, va)
+
+void test_vsnprintf_chk_s (__builtin_va_list va)
+{
+  /* Verify that specifying a size of the destination buffer that's
+     bigger than its actual size (normally determined and passed to
+     the function by __builtin_object_size) is diagnosed.  */
+  __builtin___snprintf_chk (buffer, 123, 0, 122, " ");   /* { dg-warning "destination size .123. exceeds the size of the object .122." } */
+
+  __builtin___snprintf_chk (buffer, __SIZE_MAX__, 0, 2, " ");   /* { dg-warning "destination size .* too large" } */
+
+  T (0, "%s");
+  T (1, "%s");
+  T (1, "%1s");             /* { dg-warning "output truncated while copying format string" } */
+
+  T (2, "%s%s");
+  T (2, "%s%s_");
+  T (2, "%s_%s");
+  T (2, "_%s%s");
+  T (2, "_%s_%s");          /* { dg-warning "output truncated while copying format string" } */
+}
diff --git a/gcc/testsuite/gcc.dg/format/c99-sprintf-length-2.c b/gcc/testsuite/gcc.dg/format/c99-sprintf-length-2.c
new file mode 100644
index 0000000..7cba13a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/format/c99-sprintf-length-2.c
@@ -0,0 +1,183 @@
+/* { dg-do compile } */
+/* { dg-options "-std=c99 -Wformat -Wformat-length=2 -ftrack-macro-expansion=0" } */
+
+/* Define line to the line number of the test case to exercise and
+   avoid exercising all the others.  */
+#ifndef LINE
+# define LINE 0
+#endif
+
+#if !__cplusplus
+typedef __WCHAR_TYPE__ wchar_t;
+#endif
+
+typedef unsigned char UChar;
+
+#if 1 // TEST_SPRINTF_CHK
+#  define T(bufsize, fmt, ...)						\
+    __builtin___sprintf_chk (buffer, 0,					\
+      (!LINE || __LINE__ == LINE) ? bufsize : __SIZE_MAX__, fmt, __VA_ARGS__)
+#elif TEST_VSNPRINTF_CHK
+#  define T(bufsize, fmt, ...)						\
+  __builtin___vsnprintf_chk (va, buffer, sizeof buffer, 0		\
+      (!LINE || __LINE__ == LINE) ? bufsize : __SIZE_MAX__, fmt, va)
+#elif TEST_VSNPRINTF
+#  define T(bufsize, fmt, ...)						\
+  __builtin_vsnprintf (va, buffer,					\
+      (!LINE || __LINE__ == LINE) ? bufsize : __SIZE_MAX__, fmt, __VA_ARGS__)
+#else
+#  define T(bufsize, fmt, ...)						\
+    __builtin_snprintf (buffer,						\
+      (!LINE || __LINE__ == LINE) ? bufsize : __SIZE_MAX__, fmt, __VA_ARGS__)
+#endif
+
+__builtin_va_list va;
+
+char buffer [256];
+
+/* Exercise buffer overflow detection with const string arguments.  */
+
+void test_s_const (void)
+{
+    /* Wide string literals are handled slightly differently than
+       at level 1.  At level 1, each wide character is assumed to
+       convert into a single byte.  At level 2, they are assumed
+       to convert into at least one byte.  */
+  T (0, "%ls",      L"");       /* { dg-warning "nul past the end" } */
+  T (1, "%ls",      L"");
+  T (1, "%ls",      L"\0");
+  T (1, "%1ls",     L"");       /* { dg-warning "nul past the end" } */
+
+  T (0, "%*ls",  0, L"");       /* { dg-warning "nul past the end" } */
+  T (1, "%*ls",  0, L"");
+  T (1, "%*ls",  0, L"\0");
+  T (1, "%*ls",  1, L"");       /* { dg-warning "nul past the end" } */
+
+  T (1, "%ls",      L"1");      /* { dg-warning "nul past the end" } */
+  T (1, "%.0ls",    L"1");
+  T (2, "%.0ls",    L"1");
+  T (2, "%.1ls",    L"1");
+
+  /* The following case is diagnosed at level 2 but not at level 1.  */
+  T (2, "%.2ls",    L"1");      /* { dg-warning "may write a terminating nul past the end" } */
+
+  /* The following three are constrained by the precision to at most
+     that many bytes of the converted wide string plus a terminating NUL.  */
+  T (2, "%.0ls",    L"1");
+  T (2, "%.1ls",    L"1");
+  T (3, "%.2ls",    L"1");
+}
+
+/* Exercise buffer overflow detection with non-const string arguments.  */
+
+void test_s_nonconst (const char *s, const wchar_t *ws)
+{
+  T (0, "%s",   s);             /* { dg-warning "into a region" } */
+  T (1, "%s",   s);             /* { dg-warning "nul past the end" } */
+  T (1, "%1s",  s);             /* { dg-warning "nul past the end" } */
+  T (1, "%.0s", s);
+  T (1, "%.1s", s);             /* { dg-warning "writing a terminating nul" } */
+
+  T (1, "%ls",  ws);            /* { dg-warning "writing a terminating nul" } */
+}
+
+  /* Exercise buffer overflow detection with non-const integer arguments.  */
+
+void test_hh_nonconst (int x)
+{
+  T (1, "%hhi",         x);     /* { dg-warning "into a region" } */
+  T (2, "%hhi",         x);     /* { dg-warning "into a region" } */
+  T (3, "%hhi",         x);     /* { dg-warning "into a region" } */
+  T (4, "%hhi",         x);     /* { dg-warning "may write a terminating nul past the end of a region of size .4." } */
+}
+
+void test_h_nonconst (int x)
+{
+  extern UChar uc;
+
+  T (1, "%hi",         uc);     /* { dg-warning "into a region" } */
+  T (2, "%hi",         uc);     /* { dg-warning "into a region" } */
+  /* Formatting an 8-bit unsigned char as a signed short (or any other
+     type with greater precision) int can write at most 3 characters.  */
+  T (3, "%hi",         uc);     /* { dg-warning "terminating nul past" } */
+  T (4, "%hi",         uc);
+
+  /* Verify that the same thing works when the int argument is cast
+     to unsigned char.  */
+  T (1, "%hi",   (UChar)x);     /* { dg-warning "into a region" } */
+  T (2, "%hi",   (UChar)x);     /* { dg-warning "into a region" } */
+  T (3, "%hi",   (UChar)x);     /* { dg-warning "may write a terminating nul past the end of a region of size .3." } */
+  T (4, "%hi",   (UChar)x);
+}
+
+void test_i_nonconst (int x)
+{
+  extern UChar uc;
+
+  T (1, "%i",          uc);     /* { dg-warning "into a region" } */
+  T (2, "%i",          uc);     /* { dg-warning "into a region" } */
+  T (3, "%i",          uc);     /* { dg-warning "terminating nul past" } */
+  T (4, "%i",          uc);
+
+  T (1, "%i",    (UChar)x);     /* { dg-warning "into a region" } */
+  T (2, "%i",    (UChar)x);     /* { dg-warning "into a region" } */
+  T (3, "%i",    (UChar)x);     /* { dg-warning "terminating nul past" } */
+  T (4, "%i",    (UChar)x);
+
+  /* Verify the same thing using a bit-field.  */
+  extern struct {
+    unsigned int  b1: 1;
+    unsigned int  b2: 2;
+    unsigned int  b3: 3;
+    unsigned int  b4: 4;
+             int sb4: 4;
+    unsigned int  b5: 5;
+    unsigned int  b6: 6;
+    unsigned int  b7: 7;
+    unsigned int  b8: 8;
+  } bf, abf[], *pbf;
+
+  T (1, "%i",       bf.b1);     /* { dg-warning "nul just past the end" } */
+  T (1, "%i",  abf [x].b1);     /* { dg-warning "nul just past the end" } */
+  T (1, "%i",     pbf->b1);     /* { dg-warning "nul just past the end" } */
+  /* A one bit bit-field can only be formatted as '0' or '1'.  Similarly,
+     two- and three-bit bit-fields can only be formatted as a single
+     decimal digit.  */
+  T (2, "%i",       bf.b1);
+  T (2, "%i",  abf [x].b1);
+  T (2, "%i",     pbf->b1);
+  T (2, "%i",       bf.b2);
+  T (2, "%i",  abf [x].b2);
+  T (2, "%i",     pbf->b2);
+  T (2, "%i",       bf.b3);
+  T (2, "%i",  abf [x].b3);
+  T (2, "%i",     pbf->b3);
+  /* A four-bit bit-field can be formatted as either one or two digits.  */
+  T (2, "%i",       bf.b4);     /* { dg-warning "nul past the end" } */
+  T (2, "%i",  abf [x].b4);     /* { dg-warning "nul past the end" } */
+  T (2, "%i",     pbf->b4);     /* { dg-warning "nul past the end" } */
+
+  T (3, "%i",       bf.b4);
+  T (3, "%i",     pbf->b4);
+  T (3, "%i",       bf.b5);
+  T (3, "%i",     pbf->b5);
+  T (3, "%i",       bf.b6);
+  T (3, "%i",     pbf->b6);
+  T (3, "%i",       bf.b7);     /* { dg-warning "nul past the end" } */
+  T (3, "%i",     pbf->b7);     /* { dg-warning "nul past the end" } */
+
+  T (1, "%i",       bf.b8);     /* { dg-warning "into a region" } */
+  T (2, "%i",       bf.b8);     /* { dg-warning "into a region" } */
+  /* Formatting an 8-bit unsigned char as a signed short (or any other
+     type with greater precision) int can write at most 3 characters.  */
+  T (3, "%i",       bf.b8);     /* { dg-warning "terminating nul past" } */
+  T (4, "%i",       bf.b8);
+
+  T (1, "%i",       bf.b8);     /* { dg-warning "into a region" } */
+  T (2, "%i",       bf.b8);     /* { dg-warning "into a region" } */
+  T (3, "%i",       bf.b8);     /* { dg-warning "terminating nul past" } */
+
+  T (2, "%i",      bf.sb4);     /* { dg-warning "terminating nul past" } */
+  T (3, "%i",      bf.sb4);
+  T (4, "%i",      bf.sb4);
+}
diff --git a/gcc/testsuite/gcc.dg/format/c99-sprintf-length-opt.c b/gcc/testsuite/gcc.dg/format/c99-sprintf-length-opt.c
new file mode 100644
index 0000000..2444e5f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/format/c99-sprintf-length-opt.c
@@ -0,0 +1,186 @@
+/* { dg-do compile } */
+/* { dg-options "-std=c99 -O2 -Wformat -Wformat-length=1 -ftrack-macro-expansion=0" } */
+
+#ifndef LINE
+#  define LINE 0
+#endif
+
+#define SCHAR_MAX   __SCHAR_MAX__
+#define SCHAR_MIN   (-SCHAR_MAX - 1)
+
+#define bos(x)  \
+  ((!LINE || __LINE__ == LINE) ? __builtin_object_size (x, 0) : __SIZE_MAX__)
+
+#define T(bufsize, fmt, ...)						\
+  do {									\
+    char *d = (char *)__builtin_malloc (bufsize);			\
+    __builtin___sprintf_chk (d, 0, bos (d), fmt, __VA_ARGS__);		\
+    sink (d);								\
+  } while (0)
+
+void __attribute__ ((noclone, noinline))
+sink (void *p)
+{
+  __builtin_free (p);
+}
+
+/* Identity function to require optimization to figure out the value
+   of the operand.  */
+int i (int x) { return x; }
+const char* s (const char *str) { return str; }
+
+void test_sprintf_chk_integer_value (void)
+{
+  T ( 1, "%i",  i (    0));         /* { dg-warning "nul just past the end" } */
+  T ( 1, "%i",  i (    1));         /* { dg-warning "nul just past the end" } */
+  T ( 1, "%i",  i (   -1));         /* { dg-warning "into a region" } */
+  T ( 1, "%i_", i (    1));         /* { dg-warning "character ._. at offset .2. just past the end" } */
+  T ( 1, "_%i", i (    1));         /* { dg-warning "into a region" } */
+  T ( 1, "_%i_",i (    1));         /* { dg-warning "into a region" } */
+  T ( 1, "%o",  i (    0));         /* { dg-warning "nul just past the end" } */
+  T ( 1, "%u",  i (    0));         /* { dg-warning "nul just past the end" } */
+  T ( 1, "%x",  i (    0));         /* { dg-warning "nul just past the end" } */
+  T ( 1, "%#x", i (    0));         /* { dg-warning "nul just past the end" } */
+  T ( 1, "%x",  i (    1));         /* { dg-warning "nul just past the end" } */
+  T ( 1, "%#x", i (    1));         /* { dg-warning "into a region" } */
+
+  T ( 2, "%i",  i (    0));
+  T ( 2, "%i",  i (    1));
+  T ( 2, "%i",  i (    9));
+  T ( 2, "%i",  i (   -1));         /* { dg-warning "nul just past the end" } */
+  T ( 2, "%i",  i (   10));         /* { dg-warning "nul just past the end" } */
+  T ( 2, "%i_", i (    0));         /* { dg-warning "nul just past the end" } */
+  T ( 2, "_%i", i (    0));         /* { dg-warning "nul just past the end" } */
+  T ( 2, "_%i_",i (    0));         /* { dg-warning "character ._. at offset .3. just past the end" } */
+  T ( 2, "%o",  i (    1));
+  T ( 2, "%o",  i (    7));
+  T ( 2, "%o",  i (  010));         /* { dg-warning "nul just past the end" } */
+  T ( 2, "%o",  i ( 0100));         /* { dg-warning "into a region" } */
+  T ( 2, "%x",  i (    1));
+  T ( 2, "%#x", i (    1));         /* { dg-warning "into a region" } */
+  T ( 2, "%x",  i (  0xa));
+  T ( 2, "%x",  i (  0xf));
+  T ( 2, "%x",  i ( 0x10));         /* { dg-warning "nul just past the end" } */
+  T ( 2, "%x",  i ( 0xff));         /* { dg-warning "nul just past the end" } */
+  T ( 2, "%x",  i (0x1ff));         /* { dg-warning "into a region" } */
+
+  T ( 3, "%i",  i (    0));
+  T ( 3, "%i",  i (    1));
+  T ( 3, "%i",  i (    9));
+  T ( 3, "%i",  i (   -9));
+  T ( 3, "%i",  i (   10));
+  T ( 3, "%i",  i (   99));
+  T ( 3, "%i",  i (  -99));         /* { dg-warning "nul just past the end" } */
+
+  T ( 8, "%8u", i (    1));         /* { dg-warning "nul just past the end" } */
+  T ( 9, "%8u", i (    1));
+}
+
+/* Functions to require optimization to figure out the range of the operand.
+   Used to verify that the checker makes use of the range information to
+   avoid diagnosing the output of sufficiently constrained arguments to
+   integer directives.  */
+
+signed char*
+range_schar (signed char *val, signed char min, signed char max)
+{
+  if (*val < min || max < *val) __builtin_abort ();
+  return val;
+}
+
+unsigned char*
+range_uchar (unsigned char *val, unsigned char min, unsigned char max)
+{
+  if (*val < min || max < *val) __builtin_abort ();
+  return val;
+}
+
+signed short*
+range_sshort (signed short *val, signed short min, signed short max)
+{
+  if (*val < min || max < *val) __builtin_abort ();
+  return val;
+}
+
+unsigned short*
+range_ushort (unsigned short *val, unsigned short min, unsigned short max)
+{
+  if (*val < min || max < *val) __builtin_abort ();
+  return val;
+}
+
+static int idx;
+
+/* Exercise ranges only in types signed and unsigned char and short.
+   No other types work due to bug 71690.  */
+
+void test_sprintf_chk_range_schar (signed char *a)
+{
+  /* Ra creates a range of signed char for A [idx].  A different
+     value is used each time to prevent the ranges from intesecting
+     one another, possibly even eliminating some tests as a result
+     of the range being empty. */
+#define R(min, max) *range_schar (a + idx++, min, max)
+
+  T ( 0, "%i",  R (0, 9));      /* { dg-warning ".%i. directive writing .1. byte into a region of size .0." } */
+  T ( 1, "%i",  R (0, 9));      /* { dg-warning "nul just past the end" } */
+  T ( 2, "%i",  R (0, 9));
+  T ( 2, "%i",  R (-1, 0));     /* { dg-warning "may write a terminating nul past the end of a region of size .2." } */
+  T ( 2, "%i",  R (9, 10));     /* { dg-warning "may write a terminating nul past the end of a region of size .2." } */
+
+  T ( 3, "%i",  R (-9,   9));
+  T ( 3, "%i",  R ( 0,  99));
+  T ( 3, "%i",  R ( 0, 100));   /* { dg-warning "may write a terminating nul past the end of a region of size .3." } */
+
+  /* The following call may write as few as 3 bytes and as many as 5.
+     It's judgment call how best to diagnose it to make the potential
+     problem clear.  */
+  T ( 3, "%i%i", R (1, 10), R (9, 10));   /* { dg-warning ".%i. directive writing between .1. and .2. bytes into a region of size .1." } */
+
+  T ( 4, "%i%i", R (10, 11), R (12, 13));   /* { dg-warning "nul just past the end" } */
+
+  T ( 5, "%i%i", R (-9, 99), R (-9, 99));
+
+  T ( 6, "%i_%i_%i", R (0, 9), R (0, 9), R (0,  9));
+  T ( 6, "%i_%i_%i", R (0, 9), R (0, 9), R (0, 10));  /* { dg-warning "may write a terminating nul past the end" } */
+  T ( 6, "%i_%i_%i", R (0, 9), R (0, 10), R (0, 9));  /* { dg-warning "may write a terminating nul past the end" } */
+  T ( 6, "%i_%i_%i", R (0, 10), R (0, 9), R (0, 9));  /* { dg-warning "may write a terminating nul past the end" } */
+  T ( 6, "%i_%i_%i", R (0, 9), R (0, 10), R (0, 10)); /* { dg-warning ".%i. directive writing between .1. and .2. bytes into a region of size .1." } */
+}
+
+void test_sprintf_chk_range_uchar (unsigned char *a, unsigned char *b)
+{
+#undef Ra
+#define Ra(min, max) *range_uchar (a + __LINE__, min, max)
+
+  T ( 0, "%i",  Ra (0,  9));   /* { dg-warning ".%i. directive writing .1. byte into a region of size .0." } */
+  T ( 1, "%i",  Ra (0,  9));   /* { dg-warning "nul just past the end" } */
+  T ( 2, "%i",  Ra (0,  9));
+  T ( 2, "%i",  Ra (9, 10));   /* { dg-warning "may write a terminating nul past the end of a region of size .2." } */
+
+  T ( 3, "%i",  Ra (0,  99));
+  T ( 3, "%i",  Ra (0, 100));  /* { dg-warning "may write a terminating nul past the end of a region of size .3." } */
+}
+
+void test_sprintf_chk_range_sshort (signed short *a, signed short *b)
+{
+#undef Ra
+#define Ra(min, max) *range_sshort (a + __LINE__, min, max)
+
+  T ( 0, "%i",  Ra ( 0, 9));     /* { dg-warning ".%i. directive writing .1. byte into a region of size .0." } */
+  T ( 1, "%i",  Ra ( 0, 1));     /* { dg-warning "nul just past the end" } */
+  T ( 1, "%i",  Ra ( 0, 9));     /* { dg-warning "nul just past the end" } */
+  T ( 2, "%i",  Ra ( 0, 1));
+  T ( 2, "%i",  Ra ( 8, 9));
+  T ( 2, "%i",  Ra ( 0, 9));
+  T ( 2, "%i",  Ra (-1, 0));     /* { dg-warning "may write a terminating nul past the end of a region of size .2." } */
+  T ( 2, "%i",  Ra ( 9, 10));    /* { dg-warning "may write a terminating nul past the end of a region of size .2." } */
+
+  T ( 3, "%i",  Ra ( 0, 99));
+  T ( 3, "%i",  Ra (99, 999));   /* { dg-warning "may write a terminating nul past the end of a region of size .3." } */
+
+  T ( 4, "%i",  Ra (  0,  999));
+  T ( 4, "%i",  Ra ( 99,  999));
+  T ( 4, "%i",  Ra (998,  999));
+  T ( 4, "%i",  Ra (999, 1000)); /* { dg-warning "may write a terminating nul past the end of a region of size .4." } */
+}

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