public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
@ 2016-07-01 18:15 Martin Sebor
  2016-07-04 10:59 ` Richard Biener
                   ` (4 more replies)
  0 siblings, 5 replies; 115+ messages in thread
From: Martin Sebor @ 2016-07-01 18:15 UTC (permalink / raw)
  To: Gcc Patch List, Joseph Myers, Jakub Jelinek, Richard Biener

[-- Attachment #1: Type: text/plain, Size: 2891 bytes --]

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.

[-- Attachment #2: gcc-49905.diff --]
[-- Type: text/x-patch, Size: 127966 bytes --]

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;
+}
 \f
 /* 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." } */
+}

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-07-01 18:15 [PATCH] - improve sprintf buffer overflow detection (middle-end/49905) Martin Sebor
@ 2016-07-04 10:59 ` Richard Biener
  2016-07-04 16:23   ` Martin Sebor
  2016-07-12  8:31 ` Florian Weimer
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 115+ messages in thread
From: Richard Biener @ 2016-07-04 10:59 UTC (permalink / raw)
  To: Martin Sebor; +Cc: Gcc Patch List, Joseph Myers, Jakub Jelinek

On Fri, 1 Jul 2016, Martin Sebor wrote:

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

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

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

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

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

Richard.

> 2) Optimization.
>    In keeping with the other -Wformat options, the checking is
>    enabled without optimization.  Especially at level 2, the
>    warnings can be useful even without it.  But to make buffer
>    sizes and non-constant argument values available in calls to
>    functions like sprintf (via __builtin_object_size) better
>    results are obtained with optimization.
> 
> 3) Truncation warnings.
>    Although calls to bounded functions like snprintf aren't subject
>    to buffer overflow, they can be subject to accidental truncation
>    when the destination buffer isn't sized appropriately.  With the
>    patch, such calls are diagnosed under the same option, but I
>    wonder if have a separate warning option for them might be
>    preferable (e.g., -Wformat-trunc=[01] or something like that).
>    Independently, it might be useful to differentiate between
>    truncating calls that check the return value and those that
>    don't.
> 
> Besides the usual testing I compiled several packages with the
> warning.  If found a few bugs in boundary cases in Binutils that
> are being fixed.
> 
> Thanks
> Martin
> 
> PS There are a few FIXME notes in the patch that I will either
> fix or remove, depending on feedback, before committing the
> patch.
> 

-- 
Richard Biener <rguenther@suse.de>
SUSE LINUX GmbH, GF: Felix Imendoerffer, Jane Smithard, Graham Norton, HRB 21284 (AG Nuernberg)

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-07-04 10:59 ` Richard Biener
@ 2016-07-04 16:23   ` Martin Sebor
  2016-07-04 16:44     ` Jakub Jelinek
  2016-07-05 10:11     ` Richard Biener
  0 siblings, 2 replies; 115+ messages in thread
From: Martin Sebor @ 2016-07-04 16:23 UTC (permalink / raw)
  To: Richard Biener; +Cc: Gcc Patch List, Joseph Myers, Jakub Jelinek

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

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

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

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

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

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

Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-07-04 16:23   ` Martin Sebor
@ 2016-07-04 16:44     ` Jakub Jelinek
  2016-07-04 18:10       ` Bernd Schmidt
  2016-07-04 18:17       ` Martin Sebor
  2016-07-05 10:11     ` Richard Biener
  1 sibling, 2 replies; 115+ messages in thread
From: Jakub Jelinek @ 2016-07-04 16:44 UTC (permalink / raw)
  To: Martin Sebor; +Cc: Richard Biener, Gcc Patch List, Joseph Myers

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

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

	Jakub

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-07-04 16:44     ` Jakub Jelinek
@ 2016-07-04 18:10       ` Bernd Schmidt
  2016-07-04 18:17       ` Martin Sebor
  1 sibling, 0 replies; 115+ messages in thread
From: Bernd Schmidt @ 2016-07-04 18:10 UTC (permalink / raw)
  To: Jakub Jelinek, Martin Sebor; +Cc: Richard Biener, Gcc Patch List, Joseph Myers

On 07/04/2016 06:44 PM, Jakub Jelinek wrote:
> On Mon, Jul 04, 2016 at 10:23:06AM -0600, Martin Sebor wrote:

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

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


Bernd

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-07-04 16:44     ` Jakub Jelinek
  2016-07-04 18:10       ` Bernd Schmidt
@ 2016-07-04 18:17       ` Martin Sebor
  1 sibling, 0 replies; 115+ messages in thread
From: Martin Sebor @ 2016-07-04 18:17 UTC (permalink / raw)
  To: Jakub Jelinek; +Cc: Richard Biener, Gcc Patch List, Joseph Myers

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

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

Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-07-04 16:23   ` Martin Sebor
  2016-07-04 16:44     ` Jakub Jelinek
@ 2016-07-05 10:11     ` Richard Biener
  2016-07-18 21:59       ` Martin Sebor
  1 sibling, 1 reply; 115+ messages in thread
From: Richard Biener @ 2016-07-05 10:11 UTC (permalink / raw)
  To: Martin Sebor; +Cc: Gcc Patch List, Joseph Myers, Jakub Jelinek

On Mon, 4 Jul 2016, Martin Sebor wrote:

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

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

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

Richard.

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

-- 
Richard Biener <rguenther@suse.de>
SUSE LINUX GmbH, GF: Felix Imendoerffer, Jane Smithard, Graham Norton, HRB 21284 (AG Nuernberg)

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-07-01 18:15 [PATCH] - improve sprintf buffer overflow detection (middle-end/49905) Martin Sebor
  2016-07-04 10:59 ` Richard Biener
@ 2016-07-12  8:31 ` Florian Weimer
  2016-07-12  9:51 ` Florian Weimer
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 115+ messages in thread
From: Florian Weimer @ 2016-07-12  8:31 UTC (permalink / raw)
  To: Martin Sebor, Gcc Patch List, Joseph Myers, Jakub Jelinek,
	Richard Biener

On 07/01/2016 08:15 PM, Martin Sebor wrote:
> diff --git a/gcc/passes.c b/gcc/passes.c
> index 0565cfa..008799c 100644
> --- a/gcc/passes.c
> +++ b/gcc/passes.c
> @@ -2425,8 +2425,15 @@ execute_pass_list_1 (opt_pass *pass)
>
>        if (cfun == NULL)
>  	return;
> +
> +      // inform (0, "executing pass: %s", pass->name);
> +
>        if (execute_one_pass (pass) && pass->sub)
> -        execute_pass_list_1 (pass->sub);
> +	{
> +	  // inform (0, "executing subpass: %s", pass->sub->name);
> +	  execute_pass_list_1 (pass->sub);
> +	}
> +
>        pass = pass->next;
>      }
>    while (pass);

This seems to be some leftover from debugging.

Florian

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-07-01 18:15 [PATCH] - improve sprintf buffer overflow detection (middle-end/49905) Martin Sebor
  2016-07-04 10:59 ` Richard Biener
  2016-07-12  8:31 ` Florian Weimer
@ 2016-07-12  9:51 ` Florian Weimer
  2016-07-12  9:54   ` Jakub Jelinek
  2016-07-12 12:32 ` Bernd Schmidt
  2016-07-14  1:57 ` Manuel López-Ibáñez
  4 siblings, 1 reply; 115+ messages in thread
From: Florian Weimer @ 2016-07-12  9:51 UTC (permalink / raw)
  To: Martin Sebor, Gcc Patch List, Joseph Myers, Jakub Jelinek,
	Richard Biener

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

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

#include <stdio.h>

void print (const char *);

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

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

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

Florian

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-07-12  9:51 ` Florian Weimer
@ 2016-07-12  9:54   ` Jakub Jelinek
  2016-07-12 10:01     ` Florian Weimer
  0 siblings, 1 reply; 115+ messages in thread
From: Jakub Jelinek @ 2016-07-12  9:54 UTC (permalink / raw)
  To: Florian Weimer; +Cc: Martin Sebor, Gcc Patch List, Joseph Myers, Richard Biener

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

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

	Jakub

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-07-12  9:54   ` Jakub Jelinek
@ 2016-07-12 10:01     ` Florian Weimer
  2016-07-12 14:45       ` Martin Sebor
  0 siblings, 1 reply; 115+ messages in thread
From: Florian Weimer @ 2016-07-12 10:01 UTC (permalink / raw)
  To: Jakub Jelinek; +Cc: Martin Sebor, Gcc Patch List, Joseph Myers, Richard Biener

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

Argh!  You are right, I swapped the arguments.

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

Florian



^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-07-01 18:15 [PATCH] - improve sprintf buffer overflow detection (middle-end/49905) Martin Sebor
                   ` (2 preceding siblings ...)
  2016-07-12  9:51 ` Florian Weimer
@ 2016-07-12 12:32 ` Bernd Schmidt
  2016-07-12 21:43   ` Martin Sebor
  2016-07-14  1:57 ` Manuel López-Ibáñez
  4 siblings, 1 reply; 115+ messages in thread
From: Bernd Schmidt @ 2016-07-12 12:32 UTC (permalink / raw)
  To: Martin Sebor, Gcc Patch List, Joseph Myers, Jakub Jelinek,
	Richard Biener

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

On the whole I think this looks good.

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

"its"

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

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

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

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

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

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

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

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

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

> +  if (prefix && absval)

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

> +/* Return a range representing the minimum and maximum number of bytes
> +   that the conversion specification SPEC will write on output for the
> +   integer argument ARG.  */

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

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

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

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

> +  /* A type of the argument to the directive, either deduced from
> +     the actual non-constant argument if one is known, or from
> +     the directive itself when none has been provided because it's
> +     a va_list.  */
> +  tree argtype = NULL_TREE;

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

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

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

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

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

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

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

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

> +      res.min = (0 < negative || spec->get_flag ('+') || spec->get_flag (' '))
> +	+ 1 /* unit */ + (prec < 0 ? 7 : prec ? prec + 1 : 0)
> +	+ 2 /* e+ */ + (logexpdigs < 2 ? 2 : logexpdigs);

Wrapping.

> +      /* The maximum depends on the magnitude of the value but it's
> +	 at most 316 bytes for double and 4940 for long double, plus
> +	 precision if non-negative, or 6.  */
> +      /* res.max = (spec->get_flag ('L') ? 4934 : 310) */
> +      /* 	+ (prec < 0 ? 6 : prec ? prec + 1 : 0); */
> +      res.max = expdigs + (prec < 0 ? 6 : prec ? prec + 1 : 0);

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

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

Wrapping.

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

Wrapping.

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

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

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

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

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

> +#pragma GCC diagnostic pop

Also don't forget this one.

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

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

> +     Setting the minimum to a negative value disables all length
> +     tracking for the remainder of the format string.
> +     Setting either of the other two members disables the exact or
> +     maximum length tracking, respectively, but continues to track
> +     the maximum.  */

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

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

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

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

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

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

80-character limit.

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

Comment terminator goes at the end of the previous line.

> +  /* The size of the destination as in snprintf(dest, size, ...).  */
> +  unsigned HOST_WIDE_INT dstsize = ~(unsigned HOST_WIDE_INT)0;
> +  /* The size of the destination determined by __builtin_object_size.  */
> +  unsigned HOST_WIDE_INT objsize = ~(unsigned HOST_WIDE_INT)0;

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

> +		  dstsize = warn_format_length < 2
> +		    ? wi::fits_uhwi_p (max) ? max.to_uhwi () : max.to_shwi ()
> +		    : wi::fits_uhwi_p (min) ? min.to_uhwi () : min.to_shwi ();

Wrapping.

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

Wrapping.

> +     formt directives or arguments encountered during processing

"format"

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

"overflow"

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

Wrapping. Also useless parens around the second condition.

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

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

Please don't format arguments like this.

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

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

Wrapping.

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

Here too.

> +  struct fmtresult {

Newline before brace?

> +are assumed to be 1 character long.  Enabling optimization will in most
> +cases improve the accuracy of the warning, although in some cases it may
> +also result in false positives.

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

> +NUL character

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

> +buffer in an inforational note following the warning.

"informational"

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

"format"

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

"example"

> diff --git a/gcc/genmodes.c b/gcc/genmodes.c
> index 788031b..79783fd 100644
> --- a/gcc/genmodes.c
> +++ b/gcc/genmodes.c
> @@ -486,7 +486,7 @@ make_vector_modes (enum mode_class cl, unsigned int width,
>  {
>    struct mode_data *m;
>    struct mode_data *v;
> -  char buf[8];
> +  char buf[12];
>    unsigned int ncomponents;
>    enum mode_class vclass = vector_class (cl);

Avoiding the warning? Real problem or false positive?

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

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

> +
> +      // inform (0, "executing pass: %s", pass->name);
> +
>        if (execute_one_pass (pass) && pass->sub)
> -        execute_pass_list_1 (pass->sub);
> +	{
> +	  // inform (0, "executing subpass: %s", pass->sub->name);
> +	  execute_pass_list_1 (pass->sub);
> +	}

This ought to be removed.

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

"intersecting"

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


Bernd

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-07-12 10:01     ` Florian Weimer
@ 2016-07-12 14:45       ` Martin Sebor
  2016-07-12 15:01         ` David Malcolm
  0 siblings, 1 reply; 115+ messages in thread
From: Martin Sebor @ 2016-07-12 14:45 UTC (permalink / raw)
  To: Florian Weimer, Jakub Jelinek
  Cc: Gcc Patch List, Joseph Myers, Richard Biener

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

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

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

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

Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-07-12 14:45       ` Martin Sebor
@ 2016-07-12 15:01         ` David Malcolm
  2016-07-12 15:59           ` Martin Sebor
  0 siblings, 1 reply; 115+ messages in thread
From: David Malcolm @ 2016-07-12 15:01 UTC (permalink / raw)
  To: Martin Sebor, Florian Weimer, Jakub Jelinek
  Cc: Gcc Patch List, Joseph Myers, Richard Biener

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

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

Should I file a PR for this?

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

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

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

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

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-07-12 15:01         ` David Malcolm
@ 2016-07-12 15:59           ` Martin Sebor
  2016-07-12 16:13             ` Manuel López-Ibáñez
  0 siblings, 1 reply; 115+ messages in thread
From: Martin Sebor @ 2016-07-12 15:59 UTC (permalink / raw)
  To: David Malcolm, Florian Weimer, Jakub Jelinek
  Cc: Gcc Patch List, Joseph Myers, Richard Biener

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

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

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

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

Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-07-12 15:59           ` Martin Sebor
@ 2016-07-12 16:13             ` Manuel López-Ibáñez
  2016-07-12 22:36               ` Martin Sebor
  0 siblings, 1 reply; 115+ messages in thread
From: Manuel López-Ibáñez @ 2016-07-12 16:13 UTC (permalink / raw)
  To: Martin Sebor, David Malcolm, Florian Weimer, Jakub Jelinek
  Cc: Gcc Patch List, Joseph Myers, Richard Biener

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

https://gcc.gnu.org/wiki/DiagnosticsGuidelines


^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-07-12 12:32 ` Bernd Schmidt
@ 2016-07-12 21:43   ` Martin Sebor
  0 siblings, 0 replies; 115+ messages in thread
From: Martin Sebor @ 2016-07-12 21:43 UTC (permalink / raw)
  To: Bernd Schmidt, Gcc Patch List, Joseph Myers, Jakub Jelinek,
	Richard Biener

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

Thanks the detailed review!

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

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

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

Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-07-12 16:13             ` Manuel López-Ibáñez
@ 2016-07-12 22:36               ` Martin Sebor
  2016-07-13  8:18                 ` Marek Polacek
  2016-07-13  9:08                 ` David Malcolm
  0 siblings, 2 replies; 115+ messages in thread
From: Martin Sebor @ 2016-07-12 22:36 UTC (permalink / raw)
  To: Manuel López-Ibáñez, David Malcolm,
	Florian Weimer, Jakub Jelinek
  Cc: Gcc Patch List, Joseph Myers, Richard Biener

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

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

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

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

Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-07-12 22:36               ` Martin Sebor
@ 2016-07-13  8:18                 ` Marek Polacek
  2016-07-13  9:08                 ` David Malcolm
  1 sibling, 0 replies; 115+ messages in thread
From: Marek Polacek @ 2016-07-13  8:18 UTC (permalink / raw)
  To: Martin Sebor
  Cc: Manuel López-Ibáñez, David Malcolm,
	Florian Weimer, Jakub Jelinek, Gcc Patch List, Joseph Myers,
	Richard Biener

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

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

	Marek

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-07-12 22:36               ` Martin Sebor
  2016-07-13  8:18                 ` Marek Polacek
@ 2016-07-13  9:08                 ` David Malcolm
  1 sibling, 0 replies; 115+ messages in thread
From: David Malcolm @ 2016-07-13  9:08 UTC (permalink / raw)
  To: Martin Sebor, Manuel López-Ibáñez, Florian Weimer,
	Jakub Jelinek
  Cc: Gcc Patch List, Joseph Myers, Richard Biener

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

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

(looks good to me, fwiw)

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-07-01 18:15 [PATCH] - improve sprintf buffer overflow detection (middle-end/49905) Martin Sebor
                   ` (3 preceding siblings ...)
  2016-07-12 12:32 ` Bernd Schmidt
@ 2016-07-14  1:57 ` Manuel López-Ibáñez
  4 siblings, 0 replies; 115+ messages in thread
From: Manuel López-Ibáñez @ 2016-07-14  1:57 UTC (permalink / raw)
  To: Martin Sebor, Gcc Patch List, Joseph Myers, Jakub Jelinek,
	Richard Biener

On 01/07/16 19:15, Martin Sebor wrote:
+	      /* Differentiate between an exact and inexact buffer overflow
+		 or truncation.  */
+	      const char *fmtstr;
+	      if (res->number_chars < 0)
+		fmtstr = info->bounded
+		  ? "output may be truncated at or before format character "
+		    "%qc at offset %qlu past the end of a region of size %qlu"
+		  : "writing format character %qc at offset %qlu "
+		    "in a region of size %qlu";
+	      else
+		fmtstr = info->bounded
+		  ? "output truncated at format character %qc at offset %qlu "
+		    "just past the end of a region of size %qlu"
+		  : "writing format character %qc at offset %qlu "
+		    "just past the end of a region of size %qlu";
+	      warning_at (loc, OPT_Wformat_length_, fmtstr,
+			  format_chars [-1], off - 1,
+			  (unsigned long)info->objsize);
+	    }


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

Cheers,
	Manuel.



^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-07-05 10:11     ` Richard Biener
@ 2016-07-18 21:59       ` Martin Sebor
  2016-07-21 20:33         ` Jeff Law
                           ` (4 more replies)
  0 siblings, 5 replies; 115+ messages in thread
From: Martin Sebor @ 2016-07-18 21:59 UTC (permalink / raw)
  To: Richard Biener, Gcc Patch List, Jakub Jelinek, Bernd Schmidt,
	David Malcolm, Manuel López-Ibáñez,
	Florian Weimer, Joseph Myers

[-- Attachment #1: Type: text/plain, Size: 3388 bytes --]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Thanks
Martin

[-- Attachment #2: gcc-49905.diff --]
[-- Type: text/x-patch, Size: 150717 bytes --]

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

gcc/c-family/ChangeLog:

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

gcc/cp/ChangeLog:

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

gcc/testsuite/ChangeLog:

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

gcc/ChangeLog:

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

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

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-07-18 21:59       ` Martin Sebor
@ 2016-07-21 20:33         ` Jeff Law
  2016-07-21 21:57           ` Martin Sebor
  2016-07-21 21:48         ` Jeff Law
                           ` (3 subsequent siblings)
  4 siblings, 1 reply; 115+ messages in thread
From: Jeff Law @ 2016-07-21 20:33 UTC (permalink / raw)
  To: Martin Sebor, Richard Biener, Gcc Patch List, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Manuel López-Ibáñez,
	Florian Weimer, Joseph Myers

On 07/18/2016 03:59 PM, Martin Sebor wrote:
>
> Unfortunately, linking with C_COMMON_OBJS isn't enough and linking
> with C_OBJS doesn't work because of multiple definitions for symbols
> like gt_ggc_mx_language_function.  I don't know this part of of GCC
> but it seems that each front end gets a set of these generated
> functions, some with the same names.  I spent a couple of hours
> trying to get it to work but eventually gave up.
Correct.  gt_ggc_mx_* are generated functions to support the garbage 
collection system.  In this specific case, I think it's the GC bits for 
struct language_function which is specific to each front-end.  So each 
front-end is going to have that function hence the multiple definitions.


> The only option left to make it work with LTO is to extract the
> checker from c-format.c and move it where it gets linked with lto1.
I think you're right.

>
> To that end, the attached patch adds the checker under its own new
> pass.  The pass runs very early without optimization, and relatively
> late with it to benefit from the passes above.  With optimization
> the pass also can (but doesn't yet) fold the return value from these
> functions into a constant.  In the Linux kernel, it folds 88 snprintf
> calls (I didn't find any instances where the whole call could be
> folded into a string constant).
So I haven't dug into the patch yet, but what (if any) interaction is 
there between the early and late passes?  Do they both emit warnings, or 
is it more that the early passe builds a candidate set which is refined 
by the later pass, or something totally different?


>
> I tested the patch with LTO but due to bug 71907 no warnings are
> issued.  Once the bug is resolved I'll re-test it and see about
> adding test cases to verify it.
Note Richi checked in a patch a couple days ago to stream the abstract 
origins, which is supposed to fix 71907.


>
> In the updated patch I tried to address the majority of everyone's
> comments, including those I got from the bug submitter.  I also
> made a number of enhancements based on the results I saw with the
> Linux kernel and other code (e.g., support for %p, additional
> heuristics to improve %s coverage, and support for POSIX numbered
> arguments).
>
> Once a patch along these lines is approved and committed, I'd like
> to enhance it by adding one or more of the following:
>
>  *  -fdump-sprintf-length option to have the pass dump details
>     about opportunities to fold expressions as well as instances
>     where the checker was unable to check a call because of lack
>     of object size or argument value or range information.
That'll definitely be useful as we've discussed in the past.

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

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

>
>  *  Enhance the heuristics usesd to find likely %s argument
>     lengths to improve the checker's coverage.
>
>  *  Add support for %n and perhaps other functions (e.g., scanf).
Also good stuff.

Onward to looking at the patch :-)

jeff

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-07-18 21:59       ` Martin Sebor
  2016-07-21 20:33         ` Jeff Law
@ 2016-07-21 21:48         ` Jeff Law
  2016-08-04 21:13           ` Martin Sebor
  2016-07-22 19:57         ` Jakub Jelinek
                           ` (2 subsequent siblings)
  4 siblings, 1 reply; 115+ messages in thread
From: Jeff Law @ 2016-07-21 21:48 UTC (permalink / raw)
  To: Martin Sebor, Richard Biener, Gcc Patch List, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Manuel López-Ibáñez,
	Florian Weimer, Joseph Myers


I saw a few places in GCC itself where you increased buffer sizes.  Were 
any of those level 1 failures?

In c.opt:

> +C ObjC C++ ObjC++ Warning Alias (Wformat-length=, 1, 0)
Can't this take on values 0, 1, 2? And if so, is the "1" above correct?

In invoke.texi we have:

+-Wno-format-contains-nul -Wno-format-extra-args -Wformat-length=2 @gol
Note Wformat-length=2.

Then in the actual documentation it says the default level is 1.
> +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.

s/that fit/than fit

In that same section is the text which says the default level is 1

I'm curious about the heuristics for level 1.  I guess if you get a 
warning at level 1, then you've got a situation where we know we'll 
write out of bounds.  level 2 is a may write out of bounds.  Which makes 
me wonder if rather than levels we should use symbolic names?

I guess my worry here is that if we don't message this right folks will 
make their code level 1 clean and think they're done.  When in reality 
all they've done is mitigate the most egregious problems.

Elsewhere in invoke.texi:

> +buffer in an inforational note following the warning.
informational I assume

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

In genmatch.c:
> +      char id[13];   /* Big enough for a 32-bit UINT_MAX.  */
In general we don't add comments to the end of a line like that.  If a 
comment is needed, put it before the declaration.  I'm not sure if a 
comment is needed here or not -- your call.

Similarly for various other places were you increased buffer lengths.


In passes.c:

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


The comments left over from debugging?

Looks like ~1/3 of the patch are tests.  Good stuff.  I'm going to 
assume those are correct.

I'll get into gimple-ssa-sprintf.c either later tonight or tomorrow. 
But so far nothing major, just nits.

Jeff

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-07-21 20:33         ` Jeff Law
@ 2016-07-21 21:57           ` Martin Sebor
  2016-07-22 19:18             ` Jeff Law
  0 siblings, 1 reply; 115+ messages in thread
From: Martin Sebor @ 2016-07-21 21:57 UTC (permalink / raw)
  To: Jeff Law, Richard Biener, Gcc Patch List, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Manuel López-Ibáñez,
	Florian Weimer, Joseph Myers

>> To that end, the attached patch adds the checker under its own new
>> pass.  The pass runs very early without optimization, and relatively
>> late with it to benefit from the passes above.  With optimization
>> the pass also can (but doesn't yet) fold the return value from these
>> functions into a constant.  In the Linux kernel, it folds 88 snprintf
>> calls (I didn't find any instances where the whole call could be
>> folded into a string constant).
> So I haven't dug into the patch yet, but what (if any) interaction is
> there between the early and late passes?  Do they both emit warnings, or
> is it more that the early passe builds a candidate set which is refined
> by the later pass, or something totally different?

Thanks for the feedback.

The pass only runs once.  Without optimization, it's invoked early,
does its thing, and is never invoked again.  With optimization,
it's also invoked early but returns without doing anything, only
to to invoked again much later on to do real work.  I wasn't sure
where among all the passes might be the best spot to insert it so
I put in the same place as Aldy's -Walloca patch because it relies
on the same prior passes.

>> I tested the patch with LTO but due to bug 71907 no warnings are
>> issued.  Once the bug is resolved I'll re-test it and see about
>> adding test cases to verify it.
> Note Richi checked in a patch a couple days ago to stream the abstract
> origins, which is supposed to fix 71907.

Thanks for the heads up.  I'll give it a whirl.

> Onward to looking at the patch :-)

Re-reading it again myself I see a few typos, commented out chunks
of code left over from debugging, and missing comments.  I'll clean
this up in the next update along with other requested changes.

Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-07-21 21:57           ` Martin Sebor
@ 2016-07-22 19:18             ` Jeff Law
  0 siblings, 0 replies; 115+ messages in thread
From: Jeff Law @ 2016-07-22 19:18 UTC (permalink / raw)
  To: Martin Sebor, Richard Biener, Gcc Patch List, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Manuel López-Ibáñez,
	Florian Weimer, Joseph Myers

On 07/21/2016 03:57 PM, Martin Sebor wrote:
>
> The pass only runs once.  Without optimization, it's invoked early,
> does its thing, and is never invoked again.  With optimization,
> it's also invoked early but returns without doing anything, only
> to to invoked again much later on to do real work.  I wasn't sure
> where among all the passes might be the best spot to insert it so
> I put in the same place as Aldy's -Walloca patch because it relies
> on the same prior passes.
Seems reasonable.  I'm not sure were best to put the late call either; 
it's a balancing act many passes have opportunities to simplify stuff in 
ways that may make a false positive go away, but many passes can also 
muck up the range information that you're likely depending on.

I'd say stick with its current position in the pipeline, then we should 
reevaluate after some of the range reimplementation lands.

jeff

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-07-18 21:59       ` Martin Sebor
  2016-07-21 20:33         ` Jeff Law
  2016-07-21 21:48         ` Jeff Law
@ 2016-07-22 19:57         ` Jakub Jelinek
  2016-07-22 22:13         ` Jeff Law
  2016-07-29 22:51         ` Joseph Myers
  4 siblings, 0 replies; 115+ messages in thread
From: Jakub Jelinek @ 2016-07-22 19:57 UTC (permalink / raw)
  To: Martin Sebor
  Cc: Richard Biener, Gcc Patch List, Bernd Schmidt, David Malcolm,
	Manuel López-Ibáñez, Florian Weimer, Joseph Myers

On Mon, Jul 18, 2016 at 03:59:11PM -0600, Martin Sebor wrote:
> +  /* Try to use __builtin_object_size although it rarely returns
> +     a useful result even for straighforward cases.  */
> +  tree ost = warn_format_length < 2
> +    ? integer_zero_node : build_int_cst (size_type_node, 2);
> +  tree args[] = { dest, ost };
> +  tree func = builtin_decl_explicit (BUILT_IN_OBJECT_SIZE);
> +  if (tree size = fold_builtin_n (UNKNOWN_LOCATION, func, args, 2, false))

What is the point of going through fold etc.?  You can just
(for ADDR_EXPR and SSA_NAME) call compute_builtin_object_size without,
and don't have to convert the result back to tree and then back to uhwi.

> +    return tree_to_uhwi (STRIP_NOPS  (size));

Formatting.
> +
> +  /* If __builtin_object_size fails to deliver, try to compute
> +     it for the very basic (but common) cases.  */
> +  if (TREE_CODE (dest) == SSA_NAME
> +      && POINTER_TYPE_P (TREE_TYPE (dest)))
> +    {
> +      gimple *def = SSA_NAME_DEF_STMT (dest);
> +      if (gimple_code (def) == GIMPLE_ASSIGN)

is_gimple_assign (def) ?

> +	{
> +	  tree_code code = gimple_assign_rhs_code (def);
> +	  if (code == POINTER_PLUS_EXPR)
> +	    {
> +	      tree off = gimple_assign_rhs2 (def);
> +	      dest = gimple_assign_rhs1 (def);
> +
> +	      if (cst_and_fits_in_hwi (off))
> +		{
> +		  unsigned HOST_WIDE_INT size = get_destination_size (dest);
> +		  if (size != HOST_WIDE_INT_M1U)
> +		    return size - tree_to_shwi (off);
> +		}

I think you need to be very careful on negative offsets here (or don't allow
them).

> +	    }

Shouldn't this have some upper bound for the recursion?
E.g. PARAM_VALUE (PARAM_MAX_SSA_NAME_QUERY_DEPTH)?
> +	}
> +    }
> +
> +  return -1;
> +}

	Jakub

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-07-18 21:59       ` Martin Sebor
                           ` (2 preceding siblings ...)
  2016-07-22 19:57         ` Jakub Jelinek
@ 2016-07-22 22:13         ` Jeff Law
  2016-08-11 23:36           ` Martin Sebor
  2016-07-29 22:51         ` Joseph Myers
  4 siblings, 1 reply; 115+ messages in thread
From: Jeff Law @ 2016-07-22 22:13 UTC (permalink / raw)
  To: Martin Sebor, Richard Biener, Gcc Patch List, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Manuel López-Ibáñez,
	Florian Weimer, Joseph Myers

Working through the new pass...  Overall it looks pretty good.  There's 
a certain level of trust I'll extend WRT getting the low level details 
right -- a thorough testsuite obviously helps there.



> +const pass_data pass_data_sprintf_length = {
> +  GIMPLE_PASS,        // pass type
> +  "sprintf_length",   // pass name
> +  OPTGROUP_NONE,      // optinfo_flags
> +  TV_NONE,            // tv_id
> +  PROP_cfg,           // properties_required
> +  0,                 // properties_provided
> +  0,                 // properties_destroyed
> +  0,                 // properties_start
> +  0,                 // properties_finish
So the worry here is that you need PROP_ssa in the late pass.  I'm not 
sure if we have the infrastructure to allow you to distinguish the two. 
I guess we could literally make it two passes with two distinct 
pass_data structures.


In handle_gimple_call, you don't handle anti ranges -- I haven't looked 
closely at canonicalization rules in VRP, so I don't know if you're 
likely to see a range like ![MININT, -1] which would be a synonym for 
[0..MAXINT].  It might be worth doing some instrumentation to see if 
you're getting useful anti-ranges with any kind of consistency.  ISTM 
the most interesting ones are going to allow you to drop or insist on a 
"-" sign.



> +      /* 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.  */
This doesn't parse well.


> +  /* Try to use __builtin_object_size although it rarely returns
> +     a useful result even for straighforward cases.  */
Hopefully you've stashed away some tests for this so that we can work on 
them independently of the sprintf checking itself?  ISTM that the 
recursive handling of POINTER_PLUS_EXPR really ought to move into the 
generic builtin_object_size handling itself as an independent change.



> +  /* Bail early if format length checking is disabled, either
> +     via a command line option, or as a result of the size of
> +     the destination object not being available, or due to
> +     format directives or arguments encountered during processing
> +     that prevent length tracking with any reliability.  */
> +
> +  if (HOST_WIDE_INT_MAX <= info.objsize)
> +      return;
I think the return is indented too deep.

> +      if (*pf == 0)
> +       {
> +         /* Incomplete directive.  */
> +         return;
> +       }
For this and the other early returns from compute_format_length, do we 
know if -Wformat will catch these errors?  Might that be a low priority 
follow-up project if it doesn't?


> +static void
> +add_bytes (const pass_sprintf_length::call_info &info,
> +          const char                           *beg,
> +          const char                           *end,
> +          format_result                        *res)
In general, don't try to line up parameters, args or variable 
declarations like you've done here.  Similarly in other places.

> +  /* if (warn_format_length */
> +  /*     && (overflowed || navail < nbytes */
> +  /*     || (1 < warn_format_length && )) */
Presumably old implementation and/or debugging code...  Please remove it.

Please check indention of the curleys & code block in the loop over the 
phi arguments in get_string_length.


In format_floating, we end up using actual host floating point 
arithmetic.  That's generally been frowned upon.  We're not doing 
anything in terms of code generation here, so ultimately that might be 
what allows us to still be safe from a cross compilation standpoint.

It's also not clear if that routine handles the other target floating 
point formats.  For things like VAX FP, I'm comfortable issuing a 
warning that this stuff isn't supported on that target.   We have other 
targets where a double might be the same size as a float.   Though I 
guess in that case the worst we get is false positives, so that may not 
be a huge issue either.  I'm not sure how to check for this stuff 
without bringing in aspects of the target though, which we'd like to avoid.

In format_integer you have target specific ifdefs (solaris and aix). 
First we want to avoid conditionally compiled code.  Second, from a 
modularity something like TARGET_AIX and TARGET_SOLARIS really aren't 
appropriate in here. I suspect what you're going to want to do is add 
something to the targetm structure that you can query and/or call here 
is the best we can do.

Again, overall it looks good.  My biggest concern is format_integer and 
format_float and the bleeding of target dependencies into this code.  To 
some degree it may be unavoidable and we can mitigate the damage with 
stuff in targetm -- I'd like to hear your thoughts.

Jeff

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-07-18 21:59       ` Martin Sebor
                           ` (3 preceding siblings ...)
  2016-07-22 22:13         ` Jeff Law
@ 2016-07-29 22:51         ` Joseph Myers
  2016-08-11 21:56           ` Martin Sebor
  4 siblings, 1 reply; 115+ messages in thread
From: Joseph Myers @ 2016-07-29 22:51 UTC (permalink / raw)
  To: Martin Sebor
  Cc: Richard Biener, Gcc Patch List, Jakub Jelinek, Bernd Schmidt,
	David Malcolm, Manuel López-Ibáñez,
	Florian Weimer

On Mon, 18 Jul 2016, Martin Sebor wrote:

> +  /* Number of exponent digits or -1 when unknown.  */
> +  int expdigs = -1;
> +  /* 1 when ARG < 0, 0 when ARG >= 0, -1 when unknown.  */
> +  int negative = -1;
> +  /* Log10 of EXPDIGS.  */
> +  int logexpdigs = 2;
> +
> +  const double log10_2 = .30102999566398119521;
> +
> +  if (arg && TREE_CODE (arg) == REAL_CST)
> +    {
> +      expdigs = real_exponent (TREE_REAL_CST_PTR (arg)) * log10_2;
> +      negative = real_isneg (TREE_REAL_CST_PTR (arg));
> +      logexpdigs = ilog (expdigs, 10);

This looks wrong for the case of a constant with a negative exponent.

Also, what if the constant has a decimal floating-point type?

> +    }
> +  else if (REAL_MODE_FORMAT (TYPE_MODE (type))->b == 2)
> +    {
> +      /* Compute T_MAX_EXP for base 2.  */
> +      expdigs = REAL_MODE_FORMAT (TYPE_MODE (type))->emax * log10_2;

Shouldn't you also compute logexpdigs here?  The comment on logexpdigs 
implies it's always meant to have a given relation to expdigs.  Or maybe 
those variables need to be split into min/max cases, since you're 
computing both min/max values below.

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

As I understand your logic, for the case of a constant expdigs and so 
logexpdigs may sometimes be 1 bigger than the correct value for that 
constant, say for 9.999e+99, so this may not actually be the minimum.

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

No, don't embed such assumptions about particular formats.  Compute 
correct max/min values based on the exponent range of the format in 
question.

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

I don't think comments should embed such assumptions, either.

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

Again, avoid embedding properties of given formats.

(I realise tree-call-cdce.c embeds some such assumptions, but that's bad 
practice that should not be repeated.)

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-07-21 21:48         ` Jeff Law
@ 2016-08-04 21:13           ` Martin Sebor
  2016-08-08 19:30             ` Jeff Law
  0 siblings, 1 reply; 115+ messages in thread
From: Martin Sebor @ 2016-08-04 21:13 UTC (permalink / raw)
  To: Jeff Law, Richard Biener, Gcc Patch List, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Manuel López-Ibáñez,
	Florian Weimer, Joseph Myers

On 07/21/2016 03:48 PM, Jeff Law wrote:
>

Sorry about the delay in responding...

> I saw a few places in GCC itself where you increased buffer sizes.  Were
> any of those level 1 failures?

Yes.  With optimization, even level 1 uses range information when
it's available.  The idea is that when the range of values of the
variable has been constrained (possibly to avoid a sprintf buffer
overflow) but wasn't constrained enough, it's a possible sign of
a bug.

In some cases such as when the resulting range is the result of
a cast from signed to unsigned (as in cp/mangle.c) it triggers
a diagnostic that wouldn't be issued in its absence.  Here's
a small example:

   char d [9];

   void f (long *x)
   {
     // warning here
     __builtin_sprintf (d, "%08lx", (unsigned long)*x);
   }

   void g (long *x)
   {
     // no warning here
     __builtin_sprintf (d, "%08lx", *x);
   }

It's probably possible to tweak the pass to try to detect this
case and avoid the warning, but I'm not sure it's worth it.  What
do you think?

>
> In c.opt:
>
>> +C ObjC C++ ObjC++ Warning Alias (Wformat-length=, 1, 0)
> Can't this take on values 0, 1, 2? And if so, is the "1" above correct?
>
> In invoke.texi we have:
>
> +-Wno-format-contains-nul -Wno-format-extra-args -Wformat-length=2 @gol
> Note Wformat-length=2.
>
> Then in the actual documentation it says the default level is 1.
>> +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.
>
> s/that fit/than fit
>
> In that same section is the text which says the default level is 1

Good catch!  The default level is meant to be 1, so the manual was
wrong. I've fixed it.

>
> I'm curious about the heuristics for level 1.  I guess if you get a
> warning at level 1, then you've got a situation where we know we'll
> write out of bounds.  level 2 is a may write out of bounds.  Which makes
> me wonder if rather than levels we should use symbolic names?
>
> I guess my worry here is that if we don't message this right folks will
> make their code level 1 clean and think they're done.  When in reality
> all they've done is mitigate the most egregious problems.

Right.  I'm not sure what the right solution is.  The biggest
difference between the levels is in the values they assume
unknown integers can take on.  Level one assumes 1, level two
assumes a value that results in the longest output (such as
INT_MIN).  This can cause false positives but in my experience
there aren't too many of those.  I would be comfortable making
level 2 the default if we had good range information.  Without
it, though, even some obviously safe cases tend to be diagnosed,
like this one:

   char d [4];

   void g (int i)
   {
     if (0 <= i && i < 1000)
       __builtin_sprintf (d, "%i", i);
   }

   warning: ‘%i’ directive writing between 1 and 11 bytes into a region 
of size 4 [-Wformat-length=]
        __builtin_sprintf (d, "%i", i);
                               ^~
   note: using the range [‘1’, ‘-2147483648’] for directive argument
   note: format output between 2 and 12 bytes into a destination of size 4

Maybe leave level 1 the default for now and bump it up to 2 when
the new and improved VRP pass is in?

> In passes.c:
>
>> +
>> +      // 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);
>> +       }
>> +
>
>
> The comments left over from debugging?

Yes.  Someone else pointed it out in the first patch and I forgot
to remove it.  It's gone now.

>
> Looks like ~1/3 of the patch are tests.  Good stuff.  I'm going to
> assume those are correct.

They should be for the most part.  As we've discussed, I used actual
sprintf calls to validate them within GCC itself.  Now that I've
removed those calls from the GCC source I need to add tests to verify
that the return value computed by the pass for the sprintf calls is
what the call actually returns.  I have added a new test that does
that.  I'll post an updated patch soon.

Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-08-04 21:13           ` Martin Sebor
@ 2016-08-08 19:30             ` Jeff Law
  2016-08-12  2:14               ` Martin Sebor
  0 siblings, 1 reply; 115+ messages in thread
From: Jeff Law @ 2016-08-08 19:30 UTC (permalink / raw)
  To: Martin Sebor, Richard Biener, Gcc Patch List, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Manuel López-Ibáñez,
	Florian Weimer, Joseph Myers

On 08/04/2016 03:13 PM, Martin Sebor wrote:
> On 07/21/2016 03:48 PM, Jeff Law wrote:
>>
>
> Sorry about the delay in responding...
>
>> I saw a few places in GCC itself where you increased buffer sizes.  Were
>> any of those level 1 failures?
>
> Yes.  With optimization, even level 1 uses range information when
> it's available.  The idea is that when the range of values of the
> variable has been constrained (possibly to avoid a sprintf buffer
> overflow) but wasn't constrained enough, it's a possible sign of
> a bug.
>
> In some cases such as when the resulting range is the result of
> a cast from signed to unsigned (as in cp/mangle.c) it triggers
> a diagnostic that wouldn't be issued in its absence.  Here's
> a small example:
>
>   char d [9];
>
>   void f (long *x)
>   {
>     // warning here
>     __builtin_sprintf (d, "%08lx", (unsigned long)*x);
>   }
>
>   void g (long *x)
>   {
>     // no warning here
>     __builtin_sprintf (d, "%08lx", *x);
>   }
>
> It's probably possible to tweak the pass to try to detect this
> case and avoid the warning, but I'm not sure it's worth it.  What
> do you think?
I don't think it's worth it to detect and avoid the warning right now. 
That may change as the warning sees more widespread use after going onto 
the trunk.


>
> Right.  I'm not sure what the right solution is.  The biggest
> difference between the levels is in the values they assume
> unknown integers can take on.  Level one assumes 1, level two
> assumes a value that results in the longest output (such as
> INT_MIN).  This can cause false positives but in my experience
> there aren't too many of those.  I would be comfortable making
> level 2 the default if we had good range information.  Without
> it, though, even some obviously safe cases tend to be diagnosed,
> like this one:
>
>   char d [4];
>
>   void g (int i)
>   {
>     if (0 <= i && i < 1000)
>       __builtin_sprintf (d, "%i", i);
>   }
>
>   warning: ‘%i’ directive writing between 1 and 11 bytes into a region
> of size 4 [-Wformat-length=]
>        __builtin_sprintf (d, "%i", i);
>                               ^~
>   note: using the range [‘1’, ‘-2147483648’] for directive argument
>   note: format output between 2 and 12 bytes into a destination of size 4
>
> Maybe leave level 1 the default for now and bump it up to 2 when
> the new and improved VRP pass is in?
This is probably as much of a user education problem as anything.  I 
definitely want us to be level 1 clean for gcc-7.  I think we'll want to 
evaluate the pain of getting to level 2 clean once Andrew's work is far 
enough along to evaluate how many false positives it allows us to eliminate.

jeff

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-07-29 22:51         ` Joseph Myers
@ 2016-08-11 21:56           ` Martin Sebor
  0 siblings, 0 replies; 115+ messages in thread
From: Martin Sebor @ 2016-08-11 21:56 UTC (permalink / raw)
  To: Joseph Myers
  Cc: Richard Biener, Gcc Patch List, Jakub Jelinek, Bernd Schmidt,
	David Malcolm, Manuel López-Ibáñez,
	Florian Weimer

On 07/29/2016 04:50 PM, Joseph Myers wrote:
> On Mon, 18 Jul 2016, Martin Sebor wrote:
>
>> +  /* Number of exponent digits or -1 when unknown.  */
>> +  int expdigs = -1;
>> +  /* 1 when ARG < 0, 0 when ARG >= 0, -1 when unknown.  */
>> +  int negative = -1;
>> +  /* Log10 of EXPDIGS.  */
>> +  int logexpdigs = 2;
>> +
>> +  const double log10_2 = .30102999566398119521;
>> +
>> +  if (arg && TREE_CODE (arg) == REAL_CST)
>> +    {
>> +      expdigs = real_exponent (TREE_REAL_CST_PTR (arg)) * log10_2;
>> +      negative = real_isneg (TREE_REAL_CST_PTR (arg));
>> +      logexpdigs = ilog (expdigs, 10);

Thanks for looking at it!  The floating conversion wasn't 100%
complete and the bits that were there I had the least confidence
in so I especially appreciate a second pair of eyes on it.

>
> This looks wrong for the case of a constant with a negative exponent.

You're right -- great catch!  I've fixed it.

>
> Also, what if the constant has a decimal floating-point type?

Decimal floating point isn't handled in this initial implementation.
The checker gives up after the first directive it doesn't recognize
and stops checking the rest of the format string.  Eventually, I'd
like to enhance it to handle decimal floats and other directives
and types.

>
>> +    }
>> +  else if (REAL_MODE_FORMAT (TYPE_MODE (type))->b == 2)
>> +    {
>> +      /* Compute T_MAX_EXP for base 2.  */
>> +      expdigs = REAL_MODE_FORMAT (TYPE_MODE (type))->emax * log10_2;
>
> Shouldn't you also compute logexpdigs here?  The comment on logexpdigs
> implies it's always meant to have a given relation to expdigs.  Or maybe
> those variables need to be split into min/max cases, since you're
> computing both min/max values below.

I have reworked this so logexpdigs is computed here as well.

>
>> +    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));
>
> As I understand your logic, for the case of a constant expdigs and so
> logexpdigs may sometimes be 1 bigger than the correct value for that
> constant, say for 9.999e+99, so this may not actually be the minimum.

Yes, %e is an approximation though I hadn't considered this case.
I reworked this part of the implementation to compute an accurate
result.

>> +    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));
>
> Again, avoid embedding properties of given formats.

I've removed the IEEE 754 assumptions from both the code and the
comments.

I will post the latest version of the patch shortly.

Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-07-22 22:13         ` Jeff Law
@ 2016-08-11 23:36           ` Martin Sebor
  0 siblings, 0 replies; 115+ messages in thread
From: Martin Sebor @ 2016-08-11 23:36 UTC (permalink / raw)
  To: Jeff Law, Richard Biener, Gcc Patch List, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Manuel López-Ibáñez,
	Florian Weimer, Joseph Myers

On 07/22/2016 04:12 PM, Jeff Law wrote:
> Working through the new pass...  Overall it looks pretty good.  There's
> a certain level of trust I'll extend WRT getting the low level details
> right -- a thorough testsuite obviously helps there.

In the latest patch where I add the return value optimization I also
add a new test that compares the return value computed by the pass
to the value returned by the library call.  That greatly increases
my confidence in the correctness of the pass (though the test still
could stand to be enhanced in a number of ways).

>> +const pass_data pass_data_sprintf_length = {
>> +  GIMPLE_PASS,        // pass type
>> +  "sprintf_length",   // pass name
>> +  OPTGROUP_NONE,      // optinfo_flags
>> +  TV_NONE,            // tv_id
>> +  PROP_cfg,           // properties_required
>> +  0,                 // properties_provided
>> +  0,                 // properties_destroyed
>> +  0,                 // properties_start
>> +  0,                 // properties_finish
> So the worry here is that you need PROP_ssa in the late pass.  I'm not
> sure if we have the infrastructure to allow you to distinguish the two.
> I guess we could literally make it two passes with two distinct
> pass_data structures.

I'm not sure I understand the concern.  It sounds like you are
suggesting to add another pass_data struct with PROP_ssa among
the required properties and specify it as an argument to
the gimple_opt pass constructor.  What will it do that what
I have doesn't (make sure the ssa pass runs before this pass?)
and how can I test it?

>
> In handle_gimple_call, you don't handle anti ranges -- I haven't looked
> closely at canonicalization rules in VRP, so I don't know if you're
> likely to see a range like ![MININT, -1] which would be a synonym for
> [0..MAXINT].  It might be worth doing some instrumentation to see if
> you're getting useful anti-ranges with any kind of consistency.  ISTM
> the most interesting ones are going to allow you to drop or insist on a
> "-" sign.

I don't recall seeing any inverted ranges when I was developing
the range tests.  I had high hopes for the range information but
it turned out to be so poor that I decided not to spend time
completing this part.  Once Andrew's new VRP pass is available
I'll come back to it.  If it's as generic as it sounds this pass
will not only be a consumer but also be able to contribute to
improving it.

>> +      /* 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.  */
> This doesn't parse well.
>
>
>> +  /* Try to use __builtin_object_size although it rarely returns
>> +     a useful result even for straighforward cases.  */
> Hopefully you've stashed away some tests for this so that we can work on
> them independently of the sprintf checking itself?  ISTM that the
> recursive handling of POINTER_PLUS_EXPR really ought to move into the
> generic builtin_object_size handling itself as an independent change.

Bug 71831 tracks this limitation/enhancement.  I would expect to
be relatively easy to enhance the compute_builtin_object_size
function to return more useful results for some basic expressions
even without optimization, without running the whole objsize pass.
It seems that a good number of other features in GCC (not just
warnings) would benefit from it.

>> +  /* Bail early if format length checking is disabled, either
>> +     via a command line option, or as a result of the size of
>> +     the destination object not being available, or due to
>> +     format directives or arguments encountered during processing
>> +     that prevent length tracking with any reliability.  */
>> +
>> +  if (HOST_WIDE_INT_MAX <= info.objsize)
>> +      return;
> I think the return is indented too deep.
>
>> +      if (*pf == 0)
>> +       {
>> +         /* Incomplete directive.  */
>> +         return;
>> +       }
> For this and the other early returns from compute_format_length, do we
> know if -Wformat will catch these errors?  Might that be a low priority
> follow-up project if it doesn't?

Being invoked by the front end, -Wformat is limited to checking
constant strings, so it misses problems that a pass that runs
later could detect.  With a bit of effort (to avoid duplicate
warnings) it would be possible to enhance this pass to catch some
these as well.  I chose not to spend time on it in this version
since non-constant format strings are rare but it I agree that
it's something to look into after this patch is in.

> In format_floating, we end up using actual host floating point
> arithmetic.  That's generally been frowned upon.  We're not doing
> anything in terms of code generation here, so ultimately that might be
> what allows us to still be safe from a cross compilation standpoint.
>
> It's also not clear if that routine handles the other target floating
> point formats.  For things like VAX FP, I'm comfortable issuing a
> warning that this stuff isn't supported on that target.   We have other
> targets where a double might be the same size as a float.   Though I
> guess in that case the worst we get is false positives, so that may not
> be a huge issue either.  I'm not sure how to check for this stuff
> without bringing in aspects of the target though, which we'd like to avoid.
>
> In format_integer you have target specific ifdefs (solaris and aix).
> First we want to avoid conditionally compiled code.  Second, from a
> modularity something like TARGET_AIX and TARGET_SOLARIS really aren't
> appropriate in here. I suspect what you're going to want to do is add
> something to the targetm structure that you can query and/or call here
> is the best we can do.
>
> Again, overall it looks good.  My biggest concern is format_integer and
> format_float and the bleeding of target dependencies into this code.  To
> some degree it may be unavoidable and we can mitigate the damage with
> stuff in targetm -- I'd like to hear your thoughts.

I've reworked the patch to avoid making assumptions about the floating
point format and to remove the #ifdefs and use target hooks instead.
I decided to use MPFR for the floating point stuff.  I tried a couple
of other approaches but in the end I think the MPFR route will yield
the most reliable results.

I'm still running tests but once I'm done I'll post the updated patch
for review.

Thanks
Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-08-08 19:30             ` Jeff Law
@ 2016-08-12  2:14               ` Martin Sebor
  2016-08-12 15:48                 ` Joseph Myers
  2016-08-18 18:23                 ` Jeff Law
  0 siblings, 2 replies; 115+ messages in thread
From: Martin Sebor @ 2016-08-12  2:14 UTC (permalink / raw)
  To: Jeff Law, Richard Biener, Gcc Patch List, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Manuel López-Ibáñez,
	Florian Weimer, Joseph Myers

[-- Attachment #1: Type: text/plain, Size: 1663 bytes --]

Attached is an updated patch with changes addressing most of
the suggestions and comments I received.

The main changes/improvements are the following:

  *  New option, -fprintf-return-value, enables the sprintf return
     value optimization.  It's disabled by default in this version
     but I'm hoping to enable in the final version of the patch.
     I added it in this version mainly to be able to verify the
     correctness of the pass by comparing the sprintf return value
     computed by the pass to the expected result returned from
     the same libc call.  (Before I had to rely solely on results
     hardcoded in the tests).

  *  With the -fdump-tree-printf-return-value the pass generates
     simple dump output to indicate what has been optimized and
     what hasn't.

  *  Floating point formatting relies on mpfr_snprintf to determine
     the size of the output for known values.  This lets the pass
     avoid duplicating the tricky floating point math done by sprintf
     and getting it wrong.

  *  New target hooks remove hardcoding target-specific assumptions
     about libc implementation-specific details (%p format and printf
     floating point rounding mode).

What's missing is integration with David's latest location range
changes.  I plan to work on it next but this seemed like a good
stopping point to get feedback.

There also are still opportunities for improvements to the string
length computation (now via gimple-fold.c's get_maxval_strlen,
thanks to Jakub) to improve checking of %s directives.  But since
that's a general-purpose function that's used outside this pass
it will be a separate patch.

Thanks
Martin

[-- Attachment #2: gcc-49905.diff --]
[-- Type: text/x-patch, Size: 198446 bytes --]

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

gcc/ChangeLog:
	PR middle-end/49905
	* config/linux.h (TARGET_LIBC_PRINTF_POINTER_FORMAT): Redefine.
	* config/sol2.h (TARGET_LIBC_PRINTF_POINTER_FORMAT): Same.
	* doc/invoke.texi (-Wformat-length, -fprintf-return-value): New
	options.
	* doc/tm.texi.in (TARGET_LIBC_PRINTF_ROUND_MODE): Document.
	(TARGET_LIBC_PRINTF_ROUND_MODE): Same.
	* doc/tm.texi: Regenerate.
	* gimple-fold.h (get_range_strlen): New function.
	(get_maxval_strlen): Declare existing function.
	* gimple-fold.c (get_range_strlen): Add arguments and compute both
	maximum and minimum.
	 (get_range_strlen): Define overload.
	(get_maxval_strlen): Adjust.
	* gimple-ssa-sprintf.c: Correct/rework floating point.  Implement
	optimization.
	* passes.def (pass_sprintf_length): Add new pass.
	* genmodes.c (make_vector_modes): Increase buffer size.
	* gimplify.c (gimplify_asm_expr): Same.
	* passes.c (pass_manager::register_one_dump_file): Same.
	* print-tree.c (print_node): Same.
	* targhooks.h (default_libc_printf_round_mode): Declare new function.
	(default_libc_printf_pointer_format): Same.
	(gnu_libc_printf_pointer_format): Same.
	(solaris_libc_printf_pointer_format): Same.
	* targhooks.c (default_libc_printf_round_mode): Define new function.
	(default_libc_printf_pointer_format): Same.
	(gnu_libc_printf_pointer_format): Same.
	(solaris_libc_printf_pointer_format): Same.

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

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

gcc/go/ChangeLog:
	PR middle-end/49905
	* gofrontend/expressions.cc (Call_expression::do_flatten): Increase
	buffer size.

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

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

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-08-12  2:14               ` Martin Sebor
@ 2016-08-12 15:48                 ` Joseph Myers
  2016-08-12 16:14                   ` Martin Sebor
  2016-08-18 18:23                 ` Jeff Law
  1 sibling, 1 reply; 115+ messages in thread
From: Joseph Myers @ 2016-08-12 15:48 UTC (permalink / raw)
  To: Martin Sebor
  Cc: Jeff Law, Richard Biener, Gcc Patch List, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Manuel López-Ibáñez,
	Florian Weimer

On Thu, 11 Aug 2016, Martin Sebor wrote:

>  *  New target hooks remove hardcoding target-specific assumptions
>     about libc implementation-specific details (%p format and printf
>     floating point rounding mode).

But the rounding mode may vary at runtime; optimally the conversions 
should use the rounding mode set with fesetround.  Determining it with a 
target hook doesn't make sense.  What's more appropriate would be 
determining both rounded-up and rounded-down strings to get bounds on the 
possible length.

> +Warn about function calls with format strings that wite past the end

"write"

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-08-12 15:48                 ` Joseph Myers
@ 2016-08-12 16:14                   ` Martin Sebor
  0 siblings, 0 replies; 115+ messages in thread
From: Martin Sebor @ 2016-08-12 16:14 UTC (permalink / raw)
  To: Joseph Myers
  Cc: Jeff Law, Richard Biener, Gcc Patch List, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Manuel López-Ibáñez,
	Florian Weimer

On 08/12/2016 09:48 AM, Joseph Myers wrote:
> On Thu, 11 Aug 2016, Martin Sebor wrote:
>
>>   *  New target hooks remove hardcoding target-specific assumptions
>>      about libc implementation-specific details (%p format and printf
>>      floating point rounding mode).
>
> But the rounding mode may vary at runtime; optimally the conversions
> should use the rounding mode set with fesetround.  Determining it with a
> target hook doesn't make sense.  What's more appropriate would be
> determining both rounded-up and rounded-down strings to get bounds on the
> possible length.

Yes, that's a better solution, thanks.  Is there anything else,
particularly in the floating point area, that you think should
be changed or improved?

Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-08-12  2:14               ` Martin Sebor
  2016-08-12 15:48                 ` Joseph Myers
@ 2016-08-18 18:23                 ` Jeff Law
  2016-08-19 15:30                   ` Martin Sebor
  1 sibling, 1 reply; 115+ messages in thread
From: Jeff Law @ 2016-08-18 18:23 UTC (permalink / raw)
  To: Martin Sebor, Richard Biener, Gcc Patch List, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Manuel López-Ibáñez,
	Florian Weimer, Joseph Myers

On 08/11/2016 08:13 PM, Martin Sebor wrote:
> Attached is an updated patch with changes addressing most of
> the suggestions and comments I received.
>
> The main changes/improvements are the following:
>
>  *  New option, -fprintf-return-value, enables the sprintf return
>     value optimization.  It's disabled by default in this version
>     but I'm hoping to enable in the final version of the patch.
>     I added it in this version mainly to be able to verify the
>     correctness of the pass by comparing the sprintf return value
>     computed by the pass to the expected result returned from
>     the same libc call.  (Before I had to rely solely on results
>     hardcoded in the tests).
>
>  *  With the -fdump-tree-printf-return-value the pass generates
>     simple dump output to indicate what has been optimized and
>     what hasn't.
>
>  *  Floating point formatting relies on mpfr_snprintf to determine
>     the size of the output for known values.  This lets the pass
>     avoid duplicating the tricky floating point math done by sprintf
>     and getting it wrong.
>
>  *  New target hooks remove hardcoding target-specific assumptions
>     about libc implementation-specific details (%p format and printf
>     floating point rounding mode).
>
> What's missing is integration with David's latest location range
> changes.  I plan to work on it next but this seemed like a good
> stopping point to get feedback.
>
> There also are still opportunities for improvements to the string
> length computation (now via gimple-fold.c's get_maxval_strlen,
> thanks to Jakub) to improve checking of %s directives.  But since
> that's a general-purpose function that's used outside this pass
> it will be a separate patch.
My biggest concern with this iteration is the tight integration between 
the optimization and warning.  We generally avoid that kind of tight 
integration such that enabling the warning does not affect the 
optimization and vice-versa.

So ISTM you have to do the analysis if the optimization or warning has 
been requested.  Then you conditionalize whether or not the warnings are 
emitted by their flag and the optimization based on its flag.

I understand you're going to have some further work to do because of 
conflicts with David's patches.  With that in mind, I'd suggest a bit of 
carving things up so things can start moving forward.


Patch #1.  All the fixes to static buffer sizes that were inspired by 
your warning.  These are all approved and can go in immediately.

Patch #2. Improvement to __builtin_object_size to handle 
POINTER_PLUS_EXPR on arrays.  This is something that stands on it own 
and ought to be reviewable quickly and doesn't really belong in the 
bowels of the warning/optimization patch you're developing.

Patch #3. Core infrastructure and possibly the warning.  The reason I 
say possibly the warning is they may be intertwined enough that 
separating them makes more work than it saves.  I think the warning bits 
are largely ready to go and may just need twiddling due to conflicts 
with David's work.

Patch #4. The optimizations you've got now which I'll want to take 
another look at.  Other than the overly tight integration with the 
warning, I don't see anything inherently wrong, but I would like to take 
another look at those once #1-#3 are done and dusted.

Patch #5 and beyond: Further optimization work.






> +
> +static void
> +compute_format_length (const pass_sprintf_length::call_info &info,
> +		       format_result                        *res,
> +		       const char                           *cvtbeg,
> +		       size_t                               cvtlen,
> +		       size_t                               offset,
> +		       const conversion_spec                &spec,
> +		       tree                                 arg)
Needs function comment.  Please don't bother lining up arguments like 
that.  Just one space between type and its name.




> +/* Given a suitable result RES of a call to a formatted output function
> +   described by INFO, substitute the result for the return value of
> +   the call.  The result is suitable if the number of bytes it represents
> +   is known and exact.  */
> +
> +static void
> +try_substitute_return_value (gimple_stmt_iterator  gsi,
> +			     const pass_sprintf_length::call_info      &info,
> +			     const format_result  &res)
Single space between the type and the variable name.  I only happened to 
see this one because I was going to make a comment about this function, 
so please check elsewhere in your new code.

> +{
> +  tree lhs = gimple_get_lhs (info.callstmt);
> +  if (lhs && res.bounded && res.number_chars < HOST_WIDE_INT_MAX)
> +    {
> +      /* Replace the left-hand side of the call with the constant
> +	 result of the formatting function minus 1 for the terminating
> +	 NUL which the functions' return value does not include.  */
> +      gimple_call_set_lhs (info.callstmt, NULL_TREE);
> +      tree cst = build_int_cst (integer_type_node, res.number_chars - 1);
> +      gimple *g = gimple_build_assign (lhs, cst);
> +      gsi_insert_after (&gsi, g, GSI_NEW_STMT);
> +      update_stmt (info.callstmt);
Can you verify that SSA_NAME_DEF_STMT (lhs) points to the new statement 
rather than the gimple call statement after this transformation?   Just 
verifying in the debugger is sufficient.  Probably the easiest way would 
be to p debug_tree (lhs) -- that will print the defining statement as well.



> +  else if (dump_file)
> +    {
> +      location_t callloc = gimple_location (info.callstmt);
> +      fprintf (dump_file, "On line %i ", LOCATION_LINE (callloc));
> +      print_generic_expr (dump_file, info.func, dump_flags);
> +
> +      const char *ign = lhs ? "" : " ignored";
> +      if (res.number_chars >= HOST_WIDE_INT_MAX)
> +	fprintf (dump_file, " return value in range [%lu, %lu]%s.\n",
> +		 (unsigned long)res.number_chars_min,
> +		 (unsigned long)res.number_chars_max, ign);
> +      else
> +	fprintf (dump_file, " return value %lu%s.\n",
> +		 (unsigned long)res.number_chars, ign);
> +    }
Consider setting a range via set_range_info in the else clause.

jeff


^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-08-18 18:23                 ` Jeff Law
@ 2016-08-19 15:30                   ` Martin Sebor
  2016-08-19 15:34                     ` Jeff Law
  2016-09-09 18:57                     ` [PATCH] - improve sprintf buffer overflow detection (middle-end/49905) Ian Lance Taylor
  0 siblings, 2 replies; 115+ messages in thread
From: Martin Sebor @ 2016-08-19 15:30 UTC (permalink / raw)
  To: Jeff Law, Richard Biener, Gcc Patch List, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Manuel López-Ibáñez,
	Florian Weimer, Joseph Myers

[-- Attachment #1: Type: text/plain, Size: 2773 bytes --]

> My biggest concern with this iteration is the tight integration between
> the optimization and warning.  We generally avoid that kind of tight
> integration such that enabling the warning does not affect the
> optimization and vice-versa.
>
> So ISTM you have to do the analysis if the optimization or warning has
> been requested.  Then you conditionalize whether or not the warnings are
> emitted by their flag and the optimization based on its flag.

As we discussed in IRC yesterday, the warning and the optimization
are independent of one another, and each controlled by its own option
(-Wformat-length and -fprintf-return-value).  In light of that we've
agreed that submitting both as part of the same patch is sufficient.

>
> I understand you're going to have some further work to do because of
> conflicts with David's patches.  With that in mind, I'd suggest a bit of
> carving things up so things can start moving forward.
>
>
> Patch #1.  All the fixes to static buffer sizes that were inspired by
> your warning.  These are all approved and can go in immediately.

Attached is this patch.

>
> Patch #2. Improvement to __builtin_object_size to handle
> POINTER_PLUS_EXPR on arrays.  This is something that stands on it own
> and ought to be reviewable quickly and doesn't really belong in the
> bowels of the warning/optimization patch you're developing.

Sure.  I'll submit this patch next.

>
> Patch #3. Core infrastructure and possibly the warning.  The reason I
> say possibly the warning is they may be intertwined enough that
> separating them makes more work than it saves.  I think the warning bits
> are largely ready to go and may just need twiddling due to conflicts
> with David's work.
>
> Patch #4. The optimizations you've got now which I'll want to take
> another look at.  Other than the overly tight integration with the
> warning, I don't see anything inherently wrong, but I would like to take
> another look at those once #1-#3 are done and dusted.

As we agreed, these will be submitted as one patch (probably
next week).

>
> Patch #5 and beyond: Further optimization work.

As one of the next steps I'd like to make this feature available
to user-defined sprintf-like functions decorated with attribute
format.  To do that, I'm thinking of adding either a fourth
(optional) argument to attribute format printf indicating which
of the function arguments is the destination buffer (to compute
its size), or perhaps a new attribute under its own name.  I'm
actually leaning toward latter since I think it could be used
in other contexts as well.  I welcome comments and suggestions
on this idea.

Thanks also for the rest of the detailed comments (snipped). I'll
also take care of those requests before I submit the next patch.

Martin

[-- Attachment #2: gcc-49905-wformat-length.diff --]
[-- Type: text/x-patch, Size: 7892 bytes --]

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

	* c-ada-spec.c (dump_ada_function_declaration): Increase buffer
	size to guarantee it fits the output of the formatted function
	regardless of its arguments.

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

	* mangle.c: Increase buffer size to guarantee it fits the output
	of the formatted function regardless of its arguments.

gcc/go/ChangeLog:
2016-08-18  Martin Sebor  <msebor@redhat.com>

	* gofrontend/expressions.cc: Increase buffer size to guarantee
	it fits the output of the formatted function regardless of its
	arguments.

gcc/java/ChangeLog:
2016-08-18  Martin Sebor  <msebor@redhat.com>

	* decl.c (give_name_to_locals): Increase buffer size to guarantee
	it fits the output of the formatted function regardless of its
	arguments.
	* mangle_name.c (append_unicode_mangled_name): Same.

gcc/ChangeLog:
2016-08-18  Martin Sebor  <msebor@redhat.com>

	* genmatch.c (parser::parse_expr): Increase buffer size to guarantee
	it fits the output of the formatted function regardless of its
	arguments.
	* gcc/genmodes.c (parser::parse_expr): Same.
	* gimplify.c (gimplify_asm_expr): Same.
	* passes.c (pass_manager::register_one_dump_file): Same.
	* print-tree.c (print_node): Same.

diff --git a/gcc/c-family/c-ada-spec.c b/gcc/c-family/c-ada-spec.c
index a4e0c38..6a8e04b 100644
--- a/gcc/c-family/c-ada-spec.c
+++ b/gcc/c-family/c-ada-spec.c
@@ -1603,7 +1603,7 @@ dump_ada_function_declaration (pretty_printer *buffer, tree func,
 {
   tree arg;
   const tree node = TREE_TYPE (func);
-  char buf[16];
+  char buf[17];
   int num = 0, num_args = 0, have_args = true, have_ellipsis = false;
 
   /* Compute number of arguments.  */
diff --git a/gcc/cp/mangle.c b/gcc/cp/mangle.c
index d8b5c45..5859d62 100644
--- a/gcc/cp/mangle.c
+++ b/gcc/cp/mangle.c
@@ -1740,7 +1740,9 @@ static void
 write_real_cst (const tree value)
 {
   long target_real[4];  /* largest supported float */
-  char buffer[9];       /* eight hex digits in a 32-bit number */
+  /* Buffer for eight hex digits in a 32-bit number but big enough
+     even for 64-bit long to avoid warnings.  */
+  char buffer[17];
   int i, limit, dir;
 
   tree type = TREE_TYPE (value);
diff --git a/gcc/genmatch.c b/gcc/genmatch.c
index 02e945a..6195a3b 100644
--- a/gcc/genmatch.c
+++ b/gcc/genmatch.c
@@ -4051,7 +4051,8 @@ parser::parse_expr ()
   else if (force_capture)
     {
       unsigned num = capture_ids->elements ();
-      char id[8];
+      /* Big enough for a 32-bit UINT_MAX plus prefix.  */
+      char id[13];
       bool existed;
       sprintf (id, "__%u", num);
       capture_ids->get_or_insert (xstrdup (id), &existed);
diff --git a/gcc/genmodes.c b/gcc/genmodes.c
index 1170d4f..92ca055 100644
--- a/gcc/genmodes.c
+++ b/gcc/genmodes.c
@@ -486,7 +486,8 @@ make_vector_modes (enum mode_class cl, unsigned int width,
 {
   struct mode_data *m;
   struct mode_data *v;
-  char buf[8];
+  /* Big enough for a 32-bit UINT_MAX plus the text.  */
+  char buf[12];
   unsigned int ncomponents;
   enum mode_class vclass = vector_class (cl);
 
diff --git a/gcc/gimplify.c b/gcc/gimplify.c
index 1e43dbb..25cd019 100644
--- a/gcc/gimplify.c
+++ b/gcc/gimplify.c
@@ -5346,7 +5346,8 @@ gimplify_asm_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
 	     flexibility, split it into separate input and output
  	     operands.  */
 	  tree input;
-	  char buf[10];
+	  /* Buffer big enough to format a 32-bit UINT_MAX into.  */
+	  char buf[11];
 
 	  /* Turn the in/out constraint into an output constraint.  */
 	  char *p = xstrdup (constraint);
@@ -5356,7 +5357,7 @@ gimplify_asm_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
 	  /* And add a matching input constraint.  */
 	  if (allows_reg)
 	    {
-	      sprintf (buf, "%d", i);
+	      sprintf (buf, "%u", i);
 
 	      /* If there are multiple alternatives in the constraint,
 		 handle each of them individually.  Those that allow register
diff --git a/gcc/go/gofrontend/expressions.cc b/gcc/go/gofrontend/expressions.cc
index bdc14aa..2c76b47 100644
--- a/gcc/go/gofrontend/expressions.cc
+++ b/gcc/go/gofrontend/expressions.cc
@@ -9050,7 +9050,8 @@ Call_expression::do_flatten(Gogo* gogo, Named_object*,
       Location loc = this->location();
 
       int i = 0;
-      char buf[10];
+      /* Buffer large enough for INT_MAX plus the prefix.  */
+      char buf[14];
       for (Typed_identifier_list::const_iterator p = results->begin();
            p != results->end();
            ++p, ++i)
diff --git a/gcc/java/decl.c b/gcc/java/decl.c
index 36989d3..70eac31 100644
--- a/gcc/java/decl.c
+++ b/gcc/java/decl.c
@@ -1721,7 +1721,8 @@ give_name_to_locals (JCF *jcf)
 	    DECL_NAME (parm) = get_identifier ("this");
 	  else
 	    {
-	      char buffer[12];
+	      /* Buffer large enough for INT_MAX plus prefix.  */
+	      char buffer[15];
 	      sprintf (buffer, "ARG_%d", arg_i);
 	      DECL_NAME (parm) = get_identifier (buffer);
 	    }
diff --git a/gcc/java/mangle_name.c b/gcc/java/mangle_name.c
index 00374db..7627c5d 100644
--- a/gcc/java/mangle_name.c
+++ b/gcc/java/mangle_name.c
@@ -231,7 +231,8 @@ void
 append_gpp_mangled_name (const char *name, int len)
 {
   int encoded_len, needs_escapes;
-  char buf[6];
+  /* Buffer large enough for INT_MIN.  */
+  char buf[9];
 
   MANGLE_CXX_KEYWORDS (name, len);
 
@@ -270,7 +271,8 @@ append_unicode_mangled_name (const char *name, int len)
       /* Everything else needs encoding */
       else
 	{
-	  char buf [9];
+	  /* Buffer large enough for UINT_MAX plus the prefix.  */
+	  char buf [13];
 	  if (ch == '_' || ch == 'U')
 	    {
 	      /* Prepare to recognize __U */
diff --git a/gcc/passes.c b/gcc/passes.c
index c7d7dbe..07ebf8b 100644
--- a/gcc/passes.c
+++ b/gcc/passes.c
@@ -771,7 +771,9 @@ pass_manager::register_one_dump_file (opt_pass *pass)
 {
   char *dot_name, *flag_name, *glob_name;
   const char *name, *full_name, *prefix;
-  char num[10];
+
+  /* Buffer big enough to format a 32-bit UINT_MAX into.  */
+  char num[11];
   int flags, id;
   int optgroup_flags = OPTGROUP_NONE;
   gcc::dump_manager *dumps = m_ctxt->get_dumps ();
@@ -779,7 +781,7 @@ pass_manager::register_one_dump_file (opt_pass *pass)
   /* See below in next_pass_1.  */
   num[0] = '\0';
   if (pass->static_pass_number != -1)
-    sprintf (num, "%d", ((int) pass->static_pass_number < 0
+    sprintf (num, "%u", ((int) pass->static_pass_number < 0
 			 ? 1 : pass->static_pass_number));
 
   /* The name is both used to identify the pass for the purposes of plugins,
diff --git a/gcc/print-tree.c b/gcc/print-tree.c
index 468f1ff..d69bf2a 100644
--- a/gcc/print-tree.c
+++ b/gcc/print-tree.c
@@ -694,8 +694,10 @@ print_node (FILE *file, const char *prefix, tree node, int indent)
 	  i = 0;
 	  FOR_EACH_CALL_EXPR_ARG (arg, iter, node)
 	    {
-	      char temp[10];
-	      sprintf (temp, "arg %d", i);
+	      /* Buffer big enough to format a 32-bit UINT_MAX into, plus
+		 the text.  */
+	      char temp[15];
+	      sprintf (temp, "arg %u", i);
 	      print_node (file, temp, arg, indent + 4);
 	      i++;
 	    }
@@ -706,7 +708,9 @@ print_node (FILE *file, const char *prefix, tree node, int indent)
 
 	  for (i = 0; i < len; i++)
 	    {
-	      char temp[10];
+	      /* Buffer big enough to format a 32-bit UINT_MAX into, plus
+		 the text.  */
+	      char temp[15];
 
 	      sprintf (temp, "arg %d", i);
 	      print_node (file, temp, TREE_OPERAND (node, i), indent + 4);
@@ -814,7 +818,9 @@ print_node (FILE *file, const char *prefix, tree node, int indent)
 	  for (i = 0; i < len; i++)
 	    if (TREE_VEC_ELT (node, i))
 	      {
-		char temp[10];
+	      /* Buffer big enough to format a 32-bit UINT_MAX into, plus
+		 the text.  */
+		char temp[15];
 		sprintf (temp, "elt %d", i);
 		print_node (file, temp, TREE_VEC_ELT (node, i), indent + 4);
 	      }

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-08-19 15:30                   ` Martin Sebor
@ 2016-08-19 15:34                     ` Jeff Law
  2016-08-20  3:11                       ` Trevor Saunders
                                         ` (2 more replies)
  2016-09-09 18:57                     ` [PATCH] - improve sprintf buffer overflow detection (middle-end/49905) Ian Lance Taylor
  1 sibling, 3 replies; 115+ messages in thread
From: Jeff Law @ 2016-08-19 15:34 UTC (permalink / raw)
  To: Martin Sebor, Richard Biener, Gcc Patch List, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Manuel López-Ibáñez,
	Florian Weimer, Joseph Myers

On 08/19/2016 09:29 AM, Martin Sebor wrote:
>> My biggest concern with this iteration is the tight integration between
>> the optimization and warning.  We generally avoid that kind of tight
>> integration such that enabling the warning does not affect the
>> optimization and vice-versa.
>>
>> So ISTM you have to do the analysis if the optimization or warning has
>> been requested.  Then you conditionalize whether or not the warnings are
>> emitted by their flag and the optimization based on its flag.
>
> As we discussed in IRC yesterday, the warning and the optimization
> are independent of one another, and each controlled by its own option
> (-Wformat-length and -fprintf-return-value).  In light of that we've
> agreed that submitting both as part of the same patch is sufficient.
Right.  I must have mis-read something.  I'll look for the tidbit that 
made me think they were more intertwined than they really are.  It may 
be the case that we just want to tweak a comment.

>
>>
>> I understand you're going to have some further work to do because of
>> conflicts with David's patches.  With that in mind, I'd suggest a bit of
>> carving things up so things can start moving forward.
>>
>>
>> Patch #1.  All the fixes to static buffer sizes that were inspired by
>> your warning.  These are all approved and can go in immediately.
>
> Attached is this patch.
Approved.  Please install.


>
>>
>> Patch #2. Improvement to __builtin_object_size to handle
>> POINTER_PLUS_EXPR on arrays.  This is something that stands on it own
>> and ought to be reviewable quickly and doesn't really belong in the
>> bowels of the warning/optimization patch you're developing.
>
> Sure.  I'll submit this patch next.
Excellent.

>>
>> Patch #5 and beyond: Further optimization work.
>
> As one of the next steps I'd like to make this feature available
> to user-defined sprintf-like functions decorated with attribute
> format.  To do that, I'm thinking of adding either a fourth
> (optional) argument to attribute format printf indicating which
> of the function arguments is the destination buffer (to compute
> its size), or perhaps a new attribute under its own name.  I'm
> actually leaning toward latter since I think it could be used
> in other contexts as well.  I welcome comments and suggestions
> on this idea.
Whichever we think will be easier to use and thus encourage folks to 
annotate their code properly :-)

jeff

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-08-19 15:34                     ` Jeff Law
@ 2016-08-20  3:11                       ` Trevor Saunders
  2016-08-22 12:47                         ` Florian Weimer
  2016-08-23 21:57                       ` Martin Sebor
  2016-09-08 19:19                       ` Martin Sebor
  2 siblings, 1 reply; 115+ messages in thread
From: Trevor Saunders @ 2016-08-20  3:11 UTC (permalink / raw)
  To: Jeff Law
  Cc: Martin Sebor, Richard Biener, Gcc Patch List, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Manuel López-Ibáñez,
	Florian Weimer, Joseph Myers

> > > Patch #5 and beyond: Further optimization work.
> > 
> > As one of the next steps I'd like to make this feature available
> > to user-defined sprintf-like functions decorated with attribute
> > format.  To do that, I'm thinking of adding either a fourth
> > (optional) argument to attribute format printf indicating which
> > of the function arguments is the destination buffer (to compute
> > its size), or perhaps a new attribute under its own name.  I'm
> > actually leaning toward latter since I think it could be used
> > in other contexts as well.  I welcome comments and suggestions
> > on this idea.
> Whichever we think will be easier to use and thus encourage folks to
> annotate their code properly :-)

So, sort of related I've been thinking about writing C++ string building
classes some lately.  Where you'd have a fixed length buffer and a
pointer to the next buffer (so a degenerate rope with only one side of
the tree).  One use case for such a thing is building up strings of
assembly to output in gcc.  however one somewhat awkward bit is that we
often format assembly with printf, which isn't really great for this use
case, because you need to deal with the case the current buffer only has
space for part of the string you are formatting.  So it would be nice to
have something like an interuptable printf that tells you where in the
string it stopped formatting, and allows you to continue from there with
a new buffer.
 
 It also seems worth noting that in C++11 you can actually write a
 printf that is typesafe without the format attributes for example Tom
 has one here https://github.com/tromey/typesafe-printf.  I'm not sure
 putting such a thing in libc for C++ programs is a great idea because
 of compile time costs, but its tempting.

 Trev

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-08-20  3:11                       ` Trevor Saunders
@ 2016-08-22 12:47                         ` Florian Weimer
  0 siblings, 0 replies; 115+ messages in thread
From: Florian Weimer @ 2016-08-22 12:47 UTC (permalink / raw)
  To: Trevor Saunders
  Cc: Jeff Law, Martin Sebor, Richard Biener, Gcc Patch List,
	Jakub Jelinek, Bernd Schmidt, David Malcolm,
	Manuel López-Ibáñez, Joseph Myers

On 08/20/2016 05:18 AM, Trevor Saunders wrote:
>>>> Patch #5 and beyond: Further optimization work.
>>>
>>> As one of the next steps I'd like to make this feature available
>>> to user-defined sprintf-like functions decorated with attribute
>>> format.  To do that, I'm thinking of adding either a fourth
>>> (optional) argument to attribute format printf indicating which
>>> of the function arguments is the destination buffer (to compute
>>> its size), or perhaps a new attribute under its own name.  I'm
>>> actually leaning toward latter since I think it could be used
>>> in other contexts as well.  I welcome comments and suggestions
>>> on this idea.
>> Whichever we think will be easier to use and thus encourage folks to
>> annotate their code properly :-)
>
> So, sort of related I've been thinking about writing C++ string building
> classes some lately.  Where you'd have a fixed length buffer and a
> pointer to the next buffer (so a degenerate rope with only one side of
> the tree).  One use case for such a thing is building up strings of
> assembly to output in gcc.  however one somewhat awkward bit is that we
> often format assembly with printf, which isn't really great for this use
> case, because you need to deal with the case the current buffer only has
> space for part of the string you are formatting.  So it would be nice to
> have something like an interuptable printf that tells you where in the
> string it stopped formatting, and allows you to continue from there with
> a new buffer.

glibc provides fopencookie, which can be used to print directly to a 
custom stream.  I don't know if it would provide any speed gains because 
of the internal complexities of libio.

It could be emulated using snprintf and a temporary buffer for non-glibc 
systems.

Florian

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-08-19 15:34                     ` Jeff Law
  2016-08-20  3:11                       ` Trevor Saunders
@ 2016-08-23 21:57                       ` Martin Sebor
  2016-08-23 23:00                         ` Joseph Myers
  2016-08-23 23:42                         ` Manuel López-Ibáñez
  2016-09-08 19:19                       ` Martin Sebor
  2 siblings, 2 replies; 115+ messages in thread
From: Martin Sebor @ 2016-08-23 21:57 UTC (permalink / raw)
  To: Jeff Law, Richard Biener, Gcc Patch List, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Manuel López-Ibáñez,
	Florian Weimer, Joseph Myers

[-- Attachment #1: Type: text/plain, Size: 1725 bytes --]

Attached is the latest patch.

The most important changes from the previous revision are:

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

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

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

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

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

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

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

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

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

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

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

Martin

[-- Attachment #2: gcc-49905.diff --]
[-- Type: text/x-patch, Size: 199837 bytes --]

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

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

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

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

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

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-08-23 21:57                       ` Martin Sebor
@ 2016-08-23 23:00                         ` Joseph Myers
  2016-08-24 16:41                           ` Martin Sebor
  2016-08-23 23:42                         ` Manuel López-Ibáñez
  1 sibling, 1 reply; 115+ messages in thread
From: Joseph Myers @ 2016-08-23 23:00 UTC (permalink / raw)
  To: Martin Sebor
  Cc: Jeff Law, Richard Biener, Gcc Patch List, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Manuel López-Ibáñez,
	Florian Weimer

Some observations:

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

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

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-08-23 21:57                       ` Martin Sebor
  2016-08-23 23:00                         ` Joseph Myers
@ 2016-08-23 23:42                         ` Manuel López-Ibáñez
  2016-08-24 20:16                           ` Martin Sebor
  1 sibling, 1 reply; 115+ messages in thread
From: Manuel López-Ibáñez @ 2016-08-23 23:42 UTC (permalink / raw)
  To: Martin Sebor
  Cc: Jeff Law, Richard Biener, Gcc Patch List, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Florian Weimer, Joseph Myers

On 23 August 2016 at 22:56, Martin Sebor <msebor@gmail.com> wrote:
> Attached is the latest patch.

Some suggestions:

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

Most options that take levels are documented as:

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


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

This whole hunk is duplicated

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

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

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

Warn about calls to formatted functions, such as @code{sprintf}, that
may overflow the destination buffer, or bounded functions, such as
@code{snprintf} , that may? result in output truncation.

+GCC counts the number of bytes that each format
+string and directive within it writes into the provided buffer and, when
+it detects that more bytes that fit in the destination buffer may be output,
+it emits a warning.

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

+Directives whose arguments have values that can be
+determined at compile-time account for the exact number of bytes they write.
+Directives with arguments whose values cannot be determined are processed
+based on heuristics that depend on the @var{level} argument to the option,
+and on optimization.

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

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

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

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

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

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

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

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

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

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

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

that most closely


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


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

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

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

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

+/* Return the logarithm of X in BASE.  */
+
+static int
+ilog (unsigned HOST_WIDE_INT x, int base)
+{
+  int res = 0;
+  do {
+    ++res;
+    x /= base;
+  } while (x);
+  return res;
+}

Should this go to hwint.h ?

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-08-23 23:00                         ` Joseph Myers
@ 2016-08-24 16:41                           ` Martin Sebor
  2016-08-24 18:54                             ` Florian Weimer
                                               ` (2 more replies)
  0 siblings, 3 replies; 115+ messages in thread
From: Martin Sebor @ 2016-08-24 16:41 UTC (permalink / raw)
  To: Joseph Myers
  Cc: Jeff Law, Richard Biener, Gcc Patch List, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Manuel López-Ibáñez,
	Florian Weimer

On 08/23/2016 05:00 PM, Joseph Myers wrote:
> Some observations:
>
> * Does -fprintf-return-value allow for the possibility of snprintf failing
> because of a memory allocation failure and so returning -1 when GCC
> computed bounds on what it could return if successful?

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

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

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

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

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

   sprintf (d, "%hhu", 1000)

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

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

Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-08-24 16:41                           ` Martin Sebor
@ 2016-08-24 18:54                             ` Florian Weimer
  2016-08-24 19:19                               ` Martin Sebor
  2016-08-24 22:04                             ` Joseph Myers
  2016-09-08 19:31                             ` Jeff Law
  2 siblings, 1 reply; 115+ messages in thread
From: Florian Weimer @ 2016-08-24 18:54 UTC (permalink / raw)
  To: Martin Sebor
  Cc: Joseph Myers, Jeff Law, Richard Biener, Gcc Patch List,
	Jakub Jelinek, Bernd Schmidt, David Malcolm,
	Manuel López-Ibáñez

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

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

#include <stdio.h>

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

valgrind reports:

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

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

Florian

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-08-24 18:54                             ` Florian Weimer
@ 2016-08-24 19:19                               ` Martin Sebor
  0 siblings, 0 replies; 115+ messages in thread
From: Martin Sebor @ 2016-08-24 19:19 UTC (permalink / raw)
  To: Florian Weimer
  Cc: Joseph Myers, Jeff Law, Richard Biener, Gcc Patch List,
	Jakub Jelinek, Bernd Schmidt, David Malcolm,
	Manuel López-Ibáñez

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

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

Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-08-23 23:42                         ` Manuel López-Ibáñez
@ 2016-08-24 20:16                           ` Martin Sebor
  2016-08-24 23:15                             ` Manuel López-Ibáñez
  0 siblings, 1 reply; 115+ messages in thread
From: Martin Sebor @ 2016-08-24 20:16 UTC (permalink / raw)
  To: Manuel López-Ibáñez
  Cc: Jeff Law, Richard Biener, Gcc Patch List, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Florian Weimer, Joseph Myers

[-- Attachment #1: Type: text/plain, Size: 5360 bytes --]

On 08/23/2016 05:41 PM, Manuel López-Ibáñez wrote:
> On 23 August 2016 at 22:56, Martin Sebor <msebor@gmail.com> wrote:
>> Attached is the latest patch.
>
> Some suggestions:

Thanks for the detailed comments!

>
> --Wno-format-contains-nul -Wno-format-extra-args -Wformat-nonliteral @gol
> +-Wno-format-contains-nul -Wno-format-extra-args -Wformat-length=1 @gol
>
> Most options that take levels are documented as:
>
> -Wshift-overflow -Wshift-overflow=@var{n}
> -Wstrict-overflow -Wstrict-overflow=@var{n}

I haven't found any uses of the @var{} tag for numeric arguments
to options (e.g., -Wformat=2), only for symbolic arguments (e.g.,
-Warray-bounds=@var{n}), so I'm leaving this as is.

>
> +@item -Wformat-length
> +@itemx -Wformat-length=@var{level}
> +@opindex Wformat-length
> +@opindex Wno-format-length
> +@opindex ffreestanding
> +@opindex fno-builtin
> +@opindex Wformat-length=
>
> This whole hunk is duplicated

So it is!  That's quite strange.  I'm not sure how it happened.
Maybe the same patch got applied twice.

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

I've added that.

>
> +The @option{-Wformat-length} option causes GCC to attempt to detect calls
> +to formatted functions such as @code{sprintf} that might overflow the
> +destination buffer, or bounded functions like @code{snprintf} that result
> +in output truncation.
>
> This text is a bit too verbose and quite different from other warning
> options. A humble proposal:
>
> Warn about calls to formatted functions, such as @code{sprintf}, that
> may overflow the destination buffer, or bounded functions, such as
> @code{snprintf} , that may? result in output truncation.
>
> +GCC counts the number of bytes that each format
> +string and directive within it writes into the provided buffer and, when
> +it detects that more bytes that fit in the destination buffer may be output,
> +it emits a warning.
>
> This is saying basically the same as the first sentence. Remove it?

Okay.

>
> +Directives whose arguments have values that can be
> +determined at compile-time account for the exact number of bytes they write.
> +Directives with arguments whose values cannot be determined are processed
> +based on heuristics that depend on the @var{level} argument to the option,
> +and on optimization.
>
> When the exact number of bytes written by a format string directive
> cannot be determined at compile-time, it is estimated based on
> heuristics that depend on optimization and the @var{level} argument.
> (I swapped the two because level is the next thing explained).

Okay.

>
> A better format (and it enables searching for specific levels) would be:

Thanks. I had not realized this.  I've restructured the text to fit
this format.  I was at first worried about what indenting the option
another level would look like in the PDF but it's not bad.

> +Enabling optimization will in most
> +cases improve the accuracy of the warning, although in some cases it may
> +also result in false positives.
>
> Since the example is not about optimization. Move this paragraph after
> the examples.

I found that moving this to the end of the first/main -Wformat-
length=level paragraph worked well.

> You will save some horizontal space, save the else branch and improve
> readability by doing:
>
> if (!val && fuzzy)
> {
> }
> if (!val)
>   return false;

Agreed.

>
> +/* Describes a location range outlining a substring within a string
> +   literal.  */
> +
> +class substring_loc
...
> +const char *
> +substring_loc::get_location (location_t *out_loc) const
> +{
> +  gcc_assert (out_loc);
> +
> +  if (!g_string_concat_db)
> +    g_string_concat_db
> +      = new (ggc_alloc <string_concat_db> ()) string_concat_db ();
> +
> +  static struct cpp_reader* parse_in;
> +  if (!parse_in)
> +    {
> +      /* Create and initialize a preprocessing reader.  */
> +      parse_in = cpp_create_reader (CLK_GNUC99, ident_hash, line_table);
> +      cpp_init_iconv (parse_in);
> +    }
> +
> +  return get_source_location_for_substring (parse_in, g_string_concat_db,
> +                        m_fmt_string_loc, CPP_STRING,
> +                        m_caret_idx, m_start_idx, m_end_idx,
> +                        out_loc);
> +}
> +
>
> Ideally, the diagnostics machinery would hide all these details from
> the middle-end. Surely, most of this can be shared with c-format.c.

I agree.  The challenge is that not all the bits this depends on
(the g_string_concat_db and parse_in globals defined in the front
end) are available in the middle end.  I've been talking to David
Malcolm about how best to factor things out of c-format.c and make
it available in both parts of the compiler under a convenient API.

>
> +/* Return the logarithm of X in BASE.  */
> +
> +static int
> +ilog (unsigned HOST_WIDE_INT x, int base)
> +{
> +  int res = 0;
> +  do {
> +    ++res;
> +    x /= base;
> +  } while (x);
> +  return res;
> +}
>
> Should this go to hwint.h ?

If there are other existing or potential users it would make sense
to make it more widely available.  I'll leave that to others with
a broader view of the code base to decide (my own rule of thumb is
to keep my patches contained and avoid touching parts I don't need
to).

Thanks again!
Martin

[-- Attachment #2: gcc-49905.diff --]
[-- Type: text/x-patch, Size: 195989 bytes --]

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

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

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

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

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

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-08-24 16:41                           ` Martin Sebor
  2016-08-24 18:54                             ` Florian Weimer
@ 2016-08-24 22:04                             ` Joseph Myers
  2016-08-24 23:08                               ` Martin Sebor
  2016-09-08 19:31                             ` Jeff Law
  2 siblings, 1 reply; 115+ messages in thread
From: Joseph Myers @ 2016-08-24 22:04 UTC (permalink / raw)
  To: Martin Sebor
  Cc: Jeff Law, Richard Biener, Gcc Patch List, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Manuel López-Ibáñez,
	Florian Weimer

On Wed, 24 Aug 2016, Martin Sebor wrote:

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

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

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

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

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

Yes.

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-08-24 22:04                             ` Joseph Myers
@ 2016-08-24 23:08                               ` Martin Sebor
  2016-08-25 23:02                                 ` Joseph Myers
  0 siblings, 1 reply; 115+ messages in thread
From: Martin Sebor @ 2016-08-24 23:08 UTC (permalink / raw)
  To: Joseph Myers
  Cc: Jeff Law, Richard Biener, Gcc Patch List, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Manuel López-Ibáñez,
	Florian Weimer

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

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

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

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

Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-08-24 20:16                           ` Martin Sebor
@ 2016-08-24 23:15                             ` Manuel López-Ibáñez
  2016-09-08 19:21                               ` Jeff Law
  0 siblings, 1 reply; 115+ messages in thread
From: Manuel López-Ibáñez @ 2016-08-24 23:15 UTC (permalink / raw)
  To: Martin Sebor
  Cc: Jeff Law, Richard Biener, Gcc Patch List, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Florian Weimer, Joseph Myers

>> --Wno-format-contains-nul -Wno-format-extra-args -Wformat-nonliteral @gol
>> +-Wno-format-contains-nul -Wno-format-extra-args -Wformat-length=1 @gol
>>
>> Most options that take levels are documented as:
>>
>> -Wshift-overflow -Wshift-overflow=@var{n}
>> -Wstrict-overflow -Wstrict-overflow=@var{n}
>
>
> I haven't found any uses of the @var{} tag for numeric arguments
> to options (e.g., -Wformat=2), only for symbolic arguments (e.g.,
> -Warray-bounds=@var{n}), so I'm leaving this as is.

My suggestion was to document -Wformat-length=@var{n}. There is no
difference between -Wformat-length's levels and -Wstrict-overfllow,
-Wshift-overflow, -Wstrict-aliasing levels.
-Wformat=2 is actually the odd one. Note that its description does use:
@item -Wformat
@itemx -Wformat=@var{n}

Even if you want to be explicit and follow -Wformat, then it should be

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


> I agree.  The challenge is that not all the bits this depends on
> (the g_string_concat_db and parse_in globals defined in the front
> end) are available in the middle end.  I've been talking to David
> Malcolm about how best to factor things out of c-format.c and make
> it available in both parts of the compiler under a convenient API.

Perhaps diagnostics_context could have pointers to those, forward
defined in the .h file and include the relevant libcpp headers in
diagnostics.c (or input.c). FEs that make use of those features could
initialize them (via some API) to some existing object. Otherwise,
they will work like in your patch (but within diagnostic.c). Similar
to how we initialize the FE-specific pretty-printers.

We already depend on libcpp for line-map.c, so internally depending on
other libcpp features is not so bad. The important thing is to hide
this from the clients, so that the clients do not need to be aware of
what diagnostics.c requires. That is, the middle-end and Ada should
not include headers that include libcpp headers, but diagnostics.c can
include whatever it needs.

Otherwise, the future will be again a mess and we get further away
from ever separating the FEs from the ME.

BTW, it would be nice to explain in comments why each header needs to
be included, besides obvious ones such as tree.h and gimple.h (it
would be great if we had guidelines on how to order included headers,
why not group together all gimple*, tree*, backend-stuff, diagnostics
stuff?). On the other hand, it is unfair to nitpick your patch
regarding this when other commits do the same.

+#include "backend.h"
+#include "tree.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "ssa.h"
+#include "gimple-fold.h"
+#include "gimple-pretty-print.h"
+#include "diagnostic-core.h"

Already included in diagnostic.h

+#include "fold-const.h"
+#include "gimple-iterator.h"
+#include "tree-ssa.h"
+#include "tree-object-size.h"
+#include "params.h"
+#include "tree-cfg.h"
+#include "calls.h"
+#include "cfgloop.h"
+#include "intl.h"
+
+#include "builtins.h"
+#include "stor-layout.h"
+
+#include "realmpfr.h"
+#include "target.h"
+#include "targhooks.h"
+
+#include "cpplib.h"

Not in the ME!

+#include "input.h"

already included in coretypes.h

+#include "toplev.h"

Very few files actually need to include toplev.h. Most of them just
include it because of copy-pasting headers lists. And even for bogus
reasons (final.c:#include "toplev.h" /* exact_log2, floor_log2 */)

+#include "substring-locations.h"
+#include "diagnostic.h"

Cheers,

Manuel.

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-08-24 23:08                               ` Martin Sebor
@ 2016-08-25 23:02                                 ` Joseph Myers
  0 siblings, 0 replies; 115+ messages in thread
From: Joseph Myers @ 2016-08-25 23:02 UTC (permalink / raw)
  To: Martin Sebor
  Cc: Jeff Law, Richard Biener, Gcc Patch List, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Manuel López-Ibáñez,
	Florian Weimer

On Wed, 24 Aug 2016, Martin Sebor wrote:

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

The code may not need to care about it, but the assumptions need to be 
made clear.  I haven't tried to confirm which glibc versions had an issue 
here.

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-08-19 15:34                     ` Jeff Law
  2016-08-20  3:11                       ` Trevor Saunders
  2016-08-23 21:57                       ` Martin Sebor
@ 2016-09-08 19:19                       ` Martin Sebor
  2016-09-08 21:00                         ` David Malcolm
                                           ` (3 more replies)
  2 siblings, 4 replies; 115+ messages in thread
From: Martin Sebor @ 2016-09-08 19:19 UTC (permalink / raw)
  To: Jeff Law, Richard Biener, Gcc Patch List, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Manuel López-Ibáñez,
	Florian Weimer, Joseph Myers

[-- Attachment #1: Type: text/plain, Size: 897 bytes --]

Attached is another update to the patch to address the last round
of comments and suggestions, most notably to:

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

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

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

Martin

[-- Attachment #2: gcc-49905.diff --]
[-- Type: text/x-patch, Size: 214696 bytes --]

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

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

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

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

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

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-08-24 23:15                             ` Manuel López-Ibáñez
@ 2016-09-08 19:21                               ` Jeff Law
  0 siblings, 0 replies; 115+ messages in thread
From: Jeff Law @ 2016-09-08 19:21 UTC (permalink / raw)
  To: Manuel López-Ibáñez, Martin Sebor
  Cc: Richard Biener, Gcc Patch List, Jakub Jelinek, Bernd Schmidt,
	David Malcolm, Florian Weimer, Joseph Myers

On 08/24/2016 05:14 PM, Manuel López-Ibáñez wrote:
>
>> I agree.  The challenge is that not all the bits this depends on
>> (the g_string_concat_db and parse_in globals defined in the front
>> end) are available in the middle end.  I've been talking to David
>> Malcolm about how best to factor things out of c-format.c and make
>> it available in both parts of the compiler under a convenient API.
>
> Perhaps diagnostics_context could have pointers to those, forward
> defined in the .h file and include the relevant libcpp headers in
> diagnostics.c (or input.c). FEs that make use of those features could
> initialize them (via some API) to some existing object. Otherwise,
> they will work like in your patch (but within diagnostic.c). Similar
> to how we initialize the FE-specific pretty-printers.
>
> We already depend on libcpp for line-map.c, so internally depending on
> other libcpp features is not so bad. The important thing is to hide
> this from the clients, so that the clients do not need to be aware of
> what diagnostics.c requires. That is, the middle-end and Ada should
> not include headers that include libcpp headers, but diagnostics.c can
> include whatever it needs.
>
> Otherwise, the future will be again a mess and we get further away
> from ever separating the FEs from the ME.
Warnings which rely on things like const/copy propagation, range 
analysis, inherently belong in the the middle end.  They're next to 
useless in the front-end.

This implies that a goodly amount of what's in c-format needs to move 
and likely bits of libcpp/line-map as well.


>
> BTW, it would be nice to explain in comments why each header needs to
> be included, besides obvious ones such as tree.h and gimple.h (it
> would be great if we had guidelines on how to order included headers,
> why not group together all gimple*, tree*, backend-stuff, diagnostics
> stuff?). On the other hand, it is unfair to nitpick your patch
> regarding this when other commits do the same.
I see this as busy work and work that easily gets out of date.

I'd rather just use Andrew's header file refactoring work to be able to 
answer these kinds of questions, canonicalize include ordering and to 
eliminate unnecessary/redundant includes.  In fact, ISTM it ought to be 
run before we hit stage1 close :-)

Jeff

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-08-24 16:41                           ` Martin Sebor
  2016-08-24 18:54                             ` Florian Weimer
  2016-08-24 22:04                             ` Joseph Myers
@ 2016-09-08 19:31                             ` Jeff Law
  2016-09-08 19:39                               ` Martin Sebor
  2 siblings, 1 reply; 115+ messages in thread
From: Jeff Law @ 2016-09-08 19:31 UTC (permalink / raw)
  To: Martin Sebor, Joseph Myers
  Cc: Richard Biener, Gcc Patch List, Jakub Jelinek, Bernd Schmidt,
	David Malcolm, Manuel López-Ibáñez,
	Florian Weimer

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

Jeff

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-08 19:31                             ` Jeff Law
@ 2016-09-08 19:39                               ` Martin Sebor
  0 siblings, 0 replies; 115+ messages in thread
From: Martin Sebor @ 2016-09-08 19:39 UTC (permalink / raw)
  To: Jeff Law, Joseph Myers
  Cc: Richard Biener, Gcc Patch List, Jakub Jelinek, Bernd Schmidt,
	David Malcolm, Manuel López-Ibáñez,
	Florian Weimer

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

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

Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-08 19:19                       ` Martin Sebor
@ 2016-09-08 21:00                         ` David Malcolm
  2016-09-08 22:11                           ` Martin Sebor
  2016-09-08 22:34                         ` Joseph Myers
                                           ` (2 subsequent siblings)
  3 siblings, 1 reply; 115+ messages in thread
From: David Malcolm @ 2016-09-08 21:00 UTC (permalink / raw)
  To: Martin Sebor, Jeff Law, Richard Biener, Gcc Patch List,
	Jakub Jelinek, Bernd Schmidt, Manuel López-Ibáñez,
	Florian Weimer, Joseph Myers

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

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

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

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

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

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

and you can use:

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

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

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

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

Hope this is constructive
Dave

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-08 21:00                         ` David Malcolm
@ 2016-09-08 22:11                           ` Martin Sebor
  0 siblings, 0 replies; 115+ messages in thread
From: Martin Sebor @ 2016-09-08 22:11 UTC (permalink / raw)
  To: David Malcolm, Jeff Law, Richard Biener, Gcc Patch List,
	Jakub Jelinek, Bernd Schmidt, Manuel López-Ibáñez,
	Florian Weimer, Joseph Myers

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

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

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

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

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

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

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

Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-08 19:19                       ` Martin Sebor
  2016-09-08 21:00                         ` David Malcolm
@ 2016-09-08 22:34                         ` Joseph Myers
  2016-09-12  8:07                           ` Martin Sebor
  2016-09-16 17:07                         ` Jeff Law
  2016-09-27  0:17                         ` [BUILDROBOT] tic6x-uclinux: undefined reference to `gnu_libc_printf_pointer_format(tree_node*, char const**)' (was: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)) Jan-Benedict Glaw
  3 siblings, 1 reply; 115+ messages in thread
From: Joseph Myers @ 2016-09-08 22:34 UTC (permalink / raw)
  To: Martin Sebor
  Cc: Jeff Law, Richard Biener, Gcc Patch List, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Manuel López-Ibáñez,
	Florian Weimer

On Thu, 8 Sep 2016, Martin Sebor wrote:

> diff --git a/gcc/doc/tm.texi.in b/gcc/doc/tm.texi.in
> index da133a4..4607495 100644
> --- a/gcc/doc/tm.texi.in
> +++ b/gcc/doc/tm.texi.in
> @@ -4081,6 +4081,13 @@ In either case, it remains possible to select code-generation for the alternate
>  scheme, by means of compiler command line switches.
>  @end defmac
>  
> +@deftypefn {Target Hook} {const char *} TARGET_LIBC_PRINTF_POINTER_FORMAT (tree, const char **@var{flags})
> +A hook to determine the target @code{printf} implementation format string
> +that the most closely corresponds to the @code{%p} format directive.
> +The object pointed to by the @var{flags} is set to a string consisting
> +of recognized format flags such as the @code{'#'} character.
> +@end deftypefn

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

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

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

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

> +	inform (callloc,
> +		(nbytes + exact == 1
> +		 ? "format output %wu byte into a destination of size %wu"
> +		 : "format output %wu bytes into a destination of size %wu"),
> +		nbytes + exact, info.objsize);

You need to use G_() around both format strings in such a case; xgettext 
doesn't know how to extract them both.

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-08-19 15:30                   ` Martin Sebor
  2016-08-19 15:34                     ` Jeff Law
@ 2016-09-09 18:57                     ` Ian Lance Taylor
  2016-09-09 20:33                       ` Martin Sebor
  1 sibling, 1 reply; 115+ messages in thread
From: Ian Lance Taylor @ 2016-09-09 18:57 UTC (permalink / raw)
  To: Martin Sebor; +Cc: Gcc Patch List

On Fri, Aug 19, 2016 at 8:29 AM, Martin Sebor <msebor@gmail.com> wrote:
>> My biggest concern with this iteration is the tight integration between
>> the optimization and warning.  We generally avoid that kind of tight
>> integration such that enabling the warning does not affect the
>> optimization and vice-versa.
>>
>> So ISTM you have to do the analysis if the optimization or warning has
>> been requested.  Then you conditionalize whether or not the warnings are
>> emitted by their flag and the optimization based on its flag.
>
>
> As we discussed in IRC yesterday, the warning and the optimization
> are independent of one another, and each controlled by its own option
> (-Wformat-length and -fprintf-return-value).  In light of that we've
> agreed that submitting both as part of the same patch is sufficient.
>
>>
>> I understand you're going to have some further work to do because of
>> conflicts with David's patches.  With that in mind, I'd suggest a bit of
>> carving things up so things can start moving forward.
>>
>>
>> Patch #1.  All the fixes to static buffer sizes that were inspired by
>> your warning.  These are all approved and can go in immediately.
>
>
> Attached is this patch.

Hi, this patch changed the file gcc/go/gofrontend/expressions.cc.  As
described in gcc/go/gofrontend/README, the files in the directory
gcc/go/gofrontend should not be changed in the GCC repository.
Instead, they are mirrored into GCC from a separate repository,
https://go.googlesource.com/gofrontend .  When you need to make
changes in the gofrontend directory, you can either follow the
contribution process described as
https://golang.org/doc/gccgo_contribute.html, or simply ask me to make
the change.  Thanks.

Ian

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-09 18:57                     ` [PATCH] - improve sprintf buffer overflow detection (middle-end/49905) Ian Lance Taylor
@ 2016-09-09 20:33                       ` Martin Sebor
  0 siblings, 0 replies; 115+ messages in thread
From: Martin Sebor @ 2016-09-09 20:33 UTC (permalink / raw)
  To: Ian Lance Taylor; +Cc: Gcc Patch List

>>> Patch #1.  All the fixes to static buffer sizes that were inspired by
>>> your warning.  These are all approved and can go in immediately.
>>
>>
>> Attached is this patch.
>
> Hi, this patch changed the file gcc/go/gofrontend/expressions.cc.  As
> described in gcc/go/gofrontend/README, the files in the directory
> gcc/go/gofrontend should not be changed in the GCC repository.
> Instead, they are mirrored into GCC from a separate repository,
> https://go.googlesource.com/gofrontend .  When you need to make
> changes in the gofrontend directory, you can either follow the
> contribution process described as
> https://golang.org/doc/gccgo_contribute.html, or simply ask me to make
> the change.  Thanks.

Sorry if this caused an inconvenience. I'll keep it in mind in
the future.

Thanks
Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-08 22:34                         ` Joseph Myers
@ 2016-09-12  8:07                           ` Martin Sebor
  2016-09-12 22:11                             ` Joseph Myers
                                               ` (3 more replies)
  0 siblings, 4 replies; 115+ messages in thread
From: Martin Sebor @ 2016-09-12  8:07 UTC (permalink / raw)
  To: Gcc Patch List
  Cc: Joseph Myers, Jeff Law, Richard Biener, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Manuel López-Ibáñez,
	Florian Weimer

[-- Attachment #1: Type: text/plain, Size: 3049 bytes --]

On 09/08/2016 04:10 PM, Joseph Myers wrote:
> On Thu, 8 Sep 2016, Martin Sebor wrote:
>
>> diff --git a/gcc/doc/tm.texi.in b/gcc/doc/tm.texi.in
>> index da133a4..4607495 100644
>> --- a/gcc/doc/tm.texi.in
>> +++ b/gcc/doc/tm.texi.in
>> @@ -4081,6 +4081,13 @@ In either case, it remains possible to select code-generation for the alternate
>>   scheme, by means of compiler command line switches.
>>   @end defmac
>>
>> +@deftypefn {Target Hook} {const char *} TARGET_LIBC_PRINTF_POINTER_FORMAT (tree, const char **@var{flags})
>> +A hook to determine the target @code{printf} implementation format string
>> +that the most closely corresponds to the @code{%p} format directive.
>> +The object pointed to by the @var{flags} is set to a string consisting
>> +of recognized format flags such as the @code{'#'} character.
>> +@end deftypefn
>
> No, the substance of hook documentation should go in target.def with just
> an @hook line in tm.texi.in leading to the documentation going in tm.texi
> automatically.
>
> You appear to be defining a target macro masquerading as a hook.  Please
> don't (new target macros should be avoided where possible); use a proper
> hook.  (Maybe the settings depending on OS rather than architecture means
> it needs to be one of those whose default is a manual setting in
> target-def.h rather than automatically generated, but that should be the
> limit of deviation from the normal workings of hooks.)
>
>> +  const char *pfmt = TARGET_LIBC_PRINTF_POINTER_FORMAT (arg, &flags);
>
> With a proper hook them you'd call targetm.libc_printf_pointer_format.
>
>> +	inform (callloc,
>> +		(nbytes + exact == 1
>> +		 ? "format output %wu byte into a destination of size %wu"
>> +		 : "format output %wu bytes into a destination of size %wu"),
>> +		nbytes + exact, info.objsize);
>
> You need to use G_() around both format strings in such a case; xgettext
> doesn't know how to extract them both.

Attached is revision 8 of the patch (hopefully) adjusted as
requested above, and with a simple test with of the multiline
diagnostic directives suggested by David.  This revision also
enables the -fprintf-return-value option by default.  The libgomp
test failures I was seeing in my earlier testing must have been
caused by an older version of GMP or MPFR that I had inadvertently
use (normally I use in-tree versions downloaded and expanded there
by the download_prerequisites script but that time I forgot that
step).

David, in the way of feedback, I spent hours debugging the simple
multiline test I added.  It seems that DejaGnu is exquisitely
sensitive to whitespaces in the multiline output.  I appreciate
that spacing is essential to lining up the caret and the tildes
with the text of the program but tests fail even when all the
expected output is lined up right in the multiline directive but
off by a space (or tab) with respect to the actual output.  That
DejaGnu output doesn't make this very clear at all makes debugging
these types of issues a pain.  I wonder if there's a better to do
this.

Thanks
Martin

[-- Attachment #2: gcc-49905.diff --]
[-- Type: text/x-patch, Size: 217633 bytes --]

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

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

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

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

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

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-12  8:07                           ` Martin Sebor
@ 2016-09-12 22:11                             ` Joseph Myers
  2016-09-12 22:22                               ` Martin Sebor
  2016-09-16 18:12                             ` Jeff Law
                                               ` (2 subsequent siblings)
  3 siblings, 1 reply; 115+ messages in thread
From: Joseph Myers @ 2016-09-12 22:11 UTC (permalink / raw)
  To: Martin Sebor
  Cc: Gcc Patch List, Jeff Law, Richard Biener, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Manuel López-Ibáñez,
	Florian Weimer

On Sun, 11 Sep 2016, Martin Sebor wrote:

> Attached is revision 8 of the patch (hopefully) adjusted as
> requested above, and with a simple test with of the multiline

The ChangeLog appears not to have been updated (at least, it doesn't 
mention the changes to target.def).

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-12 22:11                             ` Joseph Myers
@ 2016-09-12 22:22                               ` Martin Sebor
  2016-09-12 23:48                                 ` Joseph Myers
  0 siblings, 1 reply; 115+ messages in thread
From: Martin Sebor @ 2016-09-12 22:22 UTC (permalink / raw)
  To: Joseph Myers
  Cc: Gcc Patch List, Jeff Law, Richard Biener, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Manuel López-Ibáñez,
	Florian Weimer

On 09/12/2016 04:08 PM, Joseph Myers wrote:
> On Sun, 11 Sep 2016, Martin Sebor wrote:
>
>> Attached is revision 8 of the patch (hopefully) adjusted as
>> requested above, and with a simple test with of the multiline
>
> The ChangeLog appears not to have been updated (at least, it doesn't
> mention the changes to target.def).

I did update the ChangeLog but missed this file.  Are there any
substantive issues with the patch that you noticed or may I take
this as your approval (modulo the ChangeLog)?

Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-12 22:22                               ` Martin Sebor
@ 2016-09-12 23:48                                 ` Joseph Myers
  0 siblings, 0 replies; 115+ messages in thread
From: Joseph Myers @ 2016-09-12 23:48 UTC (permalink / raw)
  To: Martin Sebor
  Cc: Gcc Patch List, Jeff Law, Richard Biener, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Manuel López-Ibáñez,
	Florian Weimer

On Mon, 12 Sep 2016, Martin Sebor wrote:

> On 09/12/2016 04:08 PM, Joseph Myers wrote:
> > On Sun, 11 Sep 2016, Martin Sebor wrote:
> > 
> > > Attached is revision 8 of the patch (hopefully) adjusted as
> > > requested above, and with a simple test with of the multiline
> > 
> > The ChangeLog appears not to have been updated (at least, it doesn't
> > mention the changes to target.def).
> 
> I did update the ChangeLog but missed this file.  Are there any
> substantive issues with the patch that you noticed or may I take
> this as your approval (modulo the ChangeLog)?

I have not reviewed the substance of the patch.

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-08 19:19                       ` Martin Sebor
  2016-09-08 21:00                         ` David Malcolm
  2016-09-08 22:34                         ` Joseph Myers
@ 2016-09-16 17:07                         ` Jeff Law
  2016-09-27  0:17                         ` [BUILDROBOT] tic6x-uclinux: undefined reference to `gnu_libc_printf_pointer_format(tree_node*, char const**)' (was: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)) Jan-Benedict Glaw
  3 siblings, 0 replies; 115+ messages in thread
From: Jeff Law @ 2016-09-16 17:07 UTC (permalink / raw)
  To: Martin Sebor, Richard Biener, Gcc Patch List, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Manuel López-Ibáñez,
	Florian Weimer, Joseph Myers

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

jeff

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-12  8:07                           ` Martin Sebor
  2016-09-12 22:11                             ` Joseph Myers
@ 2016-09-16 18:12                             ` Jeff Law
  2016-09-20 20:15                               ` Martin Sebor
  2016-09-16 18:47                             ` David Malcolm
  2016-09-21  7:13                             ` Markus Trippelsdorf
  3 siblings, 1 reply; 115+ messages in thread
From: Jeff Law @ 2016-09-16 18:12 UTC (permalink / raw)
  To: Martin Sebor, Gcc Patch List
  Cc: Joseph Myers, Richard Biener, Jakub Jelinek, Bernd Schmidt,
	David Malcolm, Manuel López-Ibáñez,
	Florian Weimer

On 09/11/2016 08:03 PM, Martin Sebor wrote:

> Attached is revision 8 of the patch (hopefully) adjusted as
> requested above, and with a simple test with of the multiline
> diagnostic directives suggested by David.  This revision also
> enables the -fprintf-return-value option by default.  The libgomp
> test failures I was seeing in my earlier testing must have been
> caused by an older version of GMP or MPFR that I had inadvertently
> use (normally I use in-tree versions downloaded and expanded there
> by the download_prerequisites script but that time I forgot that
> step).
Ah, my question answered.  I started looking at the multiline stuff and 
didn't read the last sentence WRT libgomp.  Sorry for the noise in my 
prior message.


>
> David, in the way of feedback, I spent hours debugging the simple
> multiline test I added.  It seems that DejaGnu is exquisitely
> sensitive to whitespaces in the multiline output.  I appreciate
> that spacing is essential to lining up the caret and the tildes
> with the text of the program but tests fail even when all the
> expected output is lined up right in the multiline directive but
> off by a space (or tab) with respect to the actual output.  That
> DejaGnu output doesn't make this very clear at all makes debugging
> these types of issues a pain.  I wonder if there's a better to do
> this.
dejagnu as a whole is a pain and trying to get the right whitespace, 
regexps, escaping is an exercise in pure torture.  How you and David 
have managed to do any multiline tests is amazing.


>
> Thanks
> Martin
>
> gcc-49905.diff
>
>
> PR middle-end/49905 - Better sanity checking on sprintf src & dest to
> 	produce warning for dodgy code
>
> gcc/ChangeLog:
> 	PR middle-end/49905
> 	* Makefile.in (OBJS): Add gimple-ssa-sprintf.o.
> 	* config/linux.h (TARGET_PRINTF_POINTER_FORMAT): Redefine.
> 	* config/linux.c (gnu_libc_printf_pointer_format): New function.
> 	* config/sol2.h (TARGET_PRINTF_POINTER_FORMAT): Same.
> 	* config/sol2.c (solaris_printf_pointer_format): New function.
> 	* doc/invoke.texi (-Wformat-length, -fprintf-return-value): New
> 	options.
> 	* doc/tm.texi.in (TARGET_PRINTF_POINTER_FORMAT): Document.
> 	* doc/tm.texi: Regenerate.
> 	* gimple-fold.h (get_range_strlen): New function.
> 	(get_maxval_strlen): Declare existing function.
> 	* gimple-fold.c (get_range_strlen): Add arguments and compute both
> 	maximum and minimum.
> 	 (get_range_strlen): Define overload.
> 	(get_maxval_strlen): Adjust.
> 	* gimple-ssa-sprintf.c: New file and pass.
> 	* passes.def (pass_sprintf_length): Add new pass.
> 	* targhooks.h (default_printf_pointer_format): Declare new function.
> 	(gnu_libc_printf_pointer_format): Same.
> 	(solaris_libc_printf_pointer_format): Same.
> 	* targhooks.c (default_printf_pointer_format): Define new function.
> 	* tree-pass.h (make_pass_sprintf_length): Declare new function.
> 	* print-tree.c: Increase buffer size.
>
> gcc/c-family/ChangeLog:
> 	PR middle-end/49905
> 	* c.opt: Add -Wformat-length and -fprintf-return-value.
>
> gcc/testsuite/ChangeLog:
> 	PR middle-end/49905
> 	* gcc.dg/builtin-stringop-chk-1.c: Adjust.
> 	* gcc.dg/tree-ssa/builtin-sprintf-warn-1.c: New test.
> 	* gcc.dg/tree-ssa/builtin-sprintf-warn-2.c: New test.
> 	* gcc.dg/tree-ssa/builtin-sprintf-warn-3.c: New test.
> 	* gcc.dg/tree-ssa/builtin-sprintf-warn-4.c: New test.
> 	* gcc.dg/tree-ssa/builtin-sprintf.c: New test.
> 	* gcc.dg/tree-ssa/builtin-sprintf-2.c: New test.
>

>
>  static bool
> -get_maxval_strlen (tree arg, tree *length, bitmap *visited, int type)
> +get_range_strlen (tree arg, tree length[2], bitmap *visited, int type,
> +		  bool fuzzy)
>  {
>    tree var, val;
>    gimple *def_stmt;
>
> +  /* The minimum and maximum length.  The MAXLEN pointer stays unchanged
> +     but MINLEN may be cleared during the execution of the function.  */
> +  tree *minlen = length;
> +  tree* const maxlen = length + 1;
Check formatting here.  I'm not sure if the formatting standards 
explicitly state how to handle the "*" when there's a qualifier between 
the type and the object.  But please check.

> +
> +	  if (warned && fmtres.argmin)
> +	    {
> +	      if (fmtres.argmin == fmtres.argmax)
> +		inform (info.fmtloc, "directive argument %qE", fmtres.argmin);
> +	      else if (fmtres.bounded)
> +		inform (info.fmtloc, "directive argument in the range [%E, %E]",
> +			fmtres.argmin, fmtres.argmax);
> +	      else
> +		inform (info.fmtloc,
> +			"using the range [%qE, %qE] for directive argument",
> +			fmtres.argmin, fmtres.argmax);
Don't you need G_ for these messages?

Can you please check the calls to fmtwarn which pass in the string as an 
argument and add G_ as needed?  I think the cases where the string is 
computed into a variable are all handled correctly, it's jut those where 
the string appears as a call argument that need double checking.  I 
think there's ~3 of them.


> +
> +/* Account for the number of bytes between BEG and END (or between
> +   BEG + strlen (BEG) when END is null) in the format string in a call
> +   to a formatted output function described by INFO.  Reflect the count
> +   in RES and issue warnings as appropriate.  */
> +
> +static void
> +add_bytes (const pass_sprintf_length::call_info &info,
> +	   const char *beg, const char *end, format_result *res)
> +{
> +      size_t len = strlen (info.fmtstr + off);
> +
> +#if 0
> +      int caret = off - !len;
> +      int beg = len ? off : 0;
> +      int end = off + len - !!len;
> +      int chr = info.fmtstr[off];
> +
> +      if (const char *env = getenv ("LOC_CARET"))
> +	caret = atoi (env);
> +      if (const char *env = getenv ("LOC_BEGIN"))
> +	beg = atoi (env);
> +      if (const char *env = getenv ("LOC_END"))
> +	end = atoi (env);
> +      if (const char *env = getenv ("CHAR"))
> +	chr = atoi (env);
> +
> +      substring_loc loc
> +	(info.fmtloc, TREE_TYPE (info.format), caret, beg, end);
> +#else
> +      substring_loc loc
> +	(info.fmtloc, TREE_TYPE (info.format), off - !len, len ? off : 0,
> +	 off + len - !!len);
> +#endif
Please remove the #if 0'd code.




> +
> +  /* Avoid the return value optimization when the behavior of the call
> +     is undefined either because any directive may have produced 4K or
> +     more of output, or the return value exceeds INT_MAX, or because
> +     the ourput overflows the destination object (but leave it enabled
s/ourput/output/


I think with the nits above fixed, this is OK for the trunk.

Thanks for your patience.

Jeff


^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-12  8:07                           ` Martin Sebor
  2016-09-12 22:11                             ` Joseph Myers
  2016-09-16 18:12                             ` Jeff Law
@ 2016-09-16 18:47                             ` David Malcolm
  2016-09-20 18:27                               ` Martin Sebor
  2016-09-21  7:13                             ` Markus Trippelsdorf
  3 siblings, 1 reply; 115+ messages in thread
From: David Malcolm @ 2016-09-16 18:47 UTC (permalink / raw)
  To: Martin Sebor, Gcc Patch List
  Cc: Joseph Myers, Jeff Law, Richard Biener, Jakub Jelinek,
	Bernd Schmidt, Manuel López-Ibáñez,
	Florian Weimer

On Sun, 2016-09-11 at 20:03 -0600, Martin Sebor wrote:
> On 09/08/2016 04:10 PM, Joseph Myers wrote:
> > On Thu, 8 Sep 2016, Martin Sebor wrote:
> > 
> > > diff --git a/gcc/doc/tm.texi.in b/gcc/doc/tm.texi.in
> > > index da133a4..4607495 100644
> > > --- a/gcc/doc/tm.texi.in
> > > +++ b/gcc/doc/tm.texi.in
> > > @@ -4081,6 +4081,13 @@ In either case, it remains possible to
> > > select code-generation for the alternate
> > >   scheme, by means of compiler command line switches.
> > >   @end defmac
> > > 
> > > +@deftypefn {Target Hook} {const char *}
> > > TARGET_LIBC_PRINTF_POINTER_FORMAT (tree, const char **@var{flags}
> > > )
> > > +A hook to determine the target @code{printf} implementation
> > > format string
> > > +that the most closely corresponds to the @code{%p} format
> > > directive.
> > > +The object pointed to by the @var{flags} is set to a string
> > > consisting
> > > +of recognized format flags such as the @code{'#'} character.
> > > +@end deftypefn
> > 
> > No, the substance of hook documentation should go in target.def
> > with just
> > an @hook line in tm.texi.in leading to the documentation going in
> > tm.texi
> > automatically.
> > 
> > You appear to be defining a target macro masquerading as a hook. 
> >  Please
> > don't (new target macros should be avoided where possible); use a
> > proper
> > hook.  (Maybe the settings depending on OS rather than architecture
> > means
> > it needs to be one of those whose default is a manual setting in
> > target-def.h rather than automatically generated, but that should
> > be the
> > limit of deviation from the normal workings of hooks.)
> > 
> > > +  const char *pfmt = TARGET_LIBC_PRINTF_POINTER_FORMAT (arg,
> > > &flags);
> > 
> > With a proper hook them you'd call
> > targetm.libc_printf_pointer_format.
> > 
> > > +	inform (callloc,
> > > +		(nbytes + exact == 1
> > > +		 ? "format output %wu byte into a destination of
> > > size %wu"
> > > +		 : "format output %wu bytes into a destination
> > > of size %wu"),
> > > +		nbytes + exact, info.objsize);
> > 
> > You need to use G_() around both format strings in such a case;
> > xgettext
> > doesn't know how to extract them both.
> 
> Attached is revision 8 of the patch (hopefully) adjusted as
> requested above, and with a simple test with of the multiline
> diagnostic directives suggested by David.  This revision also
> enables the -fprintf-return-value option by default.  The libgomp
> test failures I was seeing in my earlier testing must have been
> caused by an older version of GMP or MPFR that I had inadvertently
> use (normally I use in-tree versions downloaded and expanded there
> by the download_prerequisites script but that time I forgot that
> step).
> 
> David, in the way of feedback, I spent hours debugging the simple
> multiline test I added.  It seems that DejaGnu is exquisitely
> sensitive to whitespaces in the multiline output.  I appreciate
> that spacing is essential to lining up the caret and the tildes
> with the text of the program but tests fail even when all the
> expected output is lined up right in the multiline directive but
> off by a space (or tab) with respect to the actual output.  That
> DejaGnu output doesn't make this very clear at all makes debugging
> these types of issues a pain.  I wonder if there's a better to do
> this.

Gah; I'm sorry this was such a pain to do.

I tend to just copy&paste the stuff I want to quote directly from
Emacs's compilation buffer.

However a nit, which I think is why you had problems...


diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c
new file mode 100644
index 0000000..a601ba4
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c
@@ -0,0 +1,29 @@
+/* { dg-do compile } */
+/* { dg-options "-Wformat -Wformat-length=1 -fdiagnostics-show-caret -ftrack-macro-expansion=0" } */
+
+extern int sprintf (char*, const char*, ...);
+
+char dst [8];
+
+void test (void)
+{
+  sprintf (dst + 7, "%-s", "1");
+  /* { dg-warning "writing a terminating nul past the end of the destination" "" { target *-*-*-* } 10 }
+     { dg-message "format output 2 bytes into a destination of size 1" "" { target *-*-*-* } 10 }
+    { dg-begin-multiline-output "" }
+   sprintf (dst + 7, "%-s", "1");
+                     ^~~~~
+   sprintf (dst + 7, "%-s", "1");
+   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+    { dg-end-multiline-output "" } */
+
+  sprintf (dst + 7, "%-s", "abcd");
+  /* { dg-warning ".%-s. directive writing 4 bytes into a region of size 1" "" { target *-*-*-* } 20 }
+     { dg-message "format output 5 bytes into a destination of size 1" "" { target *-*-*-* } 20 }
+    { dg-begin-multiline-output "" }
+   sprintf (dst + 7, "%-s", "abcd");
+                      ^~~   ~~~~~~
+   sprintf (dst + 7, "%-s", "abcd");
+   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+    { dg-end-multiline-output "" } */
+}

You have two pairs of dg-begin/end-multiline-output, but I think it's
better to have four; use a single begin/end pair for each call to
diagnostic_show_locus.  Hopefully doing that ought to make things a bit
less sensitive to whitespace, and easier to debug: each begin/end can
be handled by itself, whereas by combining them it's harder for
multiline.exp to detect them.  If combined, they can only be detected
if all of the dg-warning/dg-messages work (and are thus pruned by
prune.exp), if any aren't pruned, the multiline outputs will also fail.
 Maybe this exacerbated the problem?

So something like this, grouping the 4 diagnostics together with their
diagnostic_show_locus output by opening and closing comments (line
numbers will need adjusting):

+void test (void)
+{
+  sprintf (dst + 7, "%-s", "1");
+  /* { dg-warning
"writing a terminating nul past the end of the destination" "" { target
*-*-*-* } 10 }
+     { dg-begin-multiline-output "" }
+   sprintf (dst +
7, "%-s", "1");
+                     ^~~~~
+     { dg-end-multiline
-output "" } */
+  /* { dg-message "format output 2 bytes into a
destination of size 1" "" { target *-*-*-* } 10 }
+     { dg-begin
-multiline-output "" }
+   sprintf (dst + 7, "%-s", "1");
+  
 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+     { dg-end-multiline-output "" } */
+
+
  sprintf (dst + 7, "%-s", "abcd");
+  /* { dg-warning ".%-s. directive
writing 4 bytes into a region of size 1" "" { target *-*-*-* } 20 }
+   
  { dg-begin-multiline-output "" }
+   sprintf (dst + 7, "%-s", "abcd");

+                      ^~~   ~~~~~~
+     { dg-end-multiline-output "" }
*/
+  /* { dg-message "format output 5 bytes into a destination of size
1" "" { target *-*-*-* } 20 }
+     { dg-begin-multiline-output "" }
+  
 sprintf (dst + 7, "%-s", "abcd");
+   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
     { dg-end-multiline-output "" } */
+}

Another nit, if I may: FWIW I'm not in love with the wording of the messages.  Sorry to bikeshed, but how about:
  warning: buffer overflow will occur when writing terminating NUL
and:
  note: formatted output of 2 bytes into a destination of size 1
or somesuch.

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-16 18:47                             ` David Malcolm
@ 2016-09-20 18:27                               ` Martin Sebor
  2016-09-21 19:46                                 ` Gerald Pfeifer
  2016-09-22  9:11                                 ` Rainer Orth
  0 siblings, 2 replies; 115+ messages in thread
From: Martin Sebor @ 2016-09-20 18:27 UTC (permalink / raw)
  To: David Malcolm, Gcc Patch List
  Cc: Joseph Myers, Jeff Law, Richard Biener, Jakub Jelinek,
	Bernd Schmidt, Manuel López-Ibáñez,
	Florian Weimer

>> David, in the way of feedback, I spent hours debugging the simple
>> multiline test I added.  It seems that DejaGnu is exquisitely
>> sensitive to whitespaces in the multiline output.  I appreciate
>> that spacing is essential to lining up the caret and the tildes
>> with the text of the program but tests fail even when all the
>> expected output is lined up right in the multiline directive but
>> off by a space (or tab) with respect to the actual output.  That
>> DejaGnu output doesn't make this very clear at all makes debugging
>> these types of issues a pain.  I wonder if there's a better to do
>> this.
>
> Gah; I'm sorry this was such a pain to do.
>
> I tend to just copy&paste the stuff I want to quote directly from
> Emacs's compilation buffer.

Yes, I did start with that.  The problem is that I have a script
that I run to help massage my patches to the format required by
the GCC Coding Conventions.  The script mainly replaces blocks
of spaces with tabs. So while my initial patch worked, the final
one didn't because of the tabs, and it took me hours to figure
out why.  Because I couldn't see the difference I thought it was
a bug in DejaGnu or some other tool that was different between
the two machines I was using for testing the two patches.  Until
it occurred to me to diff the test itself... (FWIW, I think this
style convention is bad and should be abolished in favor of using
only plain spaces.)

>
> However a nit, which I think is why you had problems...
>
...
>
> You have two pairs of dg-begin/end-multiline-output, but I think it's
> better to have four; use a single begin/end pair for each call to
> diagnostic_show_locus.  Hopefully doing that ought to make things a bit
> less sensitive to whitespace, and easier to debug: each begin/end can
> be handled by itself, whereas by combining them it's harder for
> multiline.exp to detect them.  If combined, they can only be detected
> if all of the dg-warning/dg-messages work (and are thus pruned by
> prune.exp), if any aren't pruned, the multiline outputs will also fail.
>  Maybe this exacerbated the problem?

Maybe.  I remember experimenting with the multiline directives when
I first added the test and was struggling to get it to work.  I think
the problems I was having were unrelated to tabs but probably had
something to do with the exact number of leading spaces.

FWIW, to make the multiline directives less prone to this type of
a problem it seems that instead of counting each leading space and
tab character and treating them as ordinary they could verify that
the first non-whitespace character on each line of the actual output
lines up with the first non-whitespace character of the expected
output with some constant offset.  That way spaces vs tabs shouldn't
matter as long as they were used consistently (or they could be made
not to matter at all if a tab was treated as a single space).  What
do you think about that?

>
> So something like this, grouping the 4 diagnostics together with their
> diagnostic_show_locus output by opening and closing comments (line
> numbers will need adjusting):

Thanks.  Let me apply it and see how it works.

>
> Another nit, if I may: FWIW I'm not in love with the wording of the messages.  Sorry to bikeshed, but how about:
>   warning: buffer overflow will occur when writing terminating NUL
> and:
>   note: formatted output of 2 bytes into a destination of size 1
> or somesuch.

I won't claim the text of the messages is perfect but I did spend
a lot of time tweaking them.  That's not to say they can't be
improved but changing them means quite a bit of work adjusting
the tests.  At this point, I'd like to focus on getting the patch
committed.  After that, if there's still time, I'm happy to take
feedback and tweak the diagnostics based on it.

Thanks again for your help and the suggestions above!

Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-16 18:12                             ` Jeff Law
@ 2016-09-20 20:15                               ` Martin Sebor
  2016-09-20 20:33                                 ` David Malcolm
  0 siblings, 1 reply; 115+ messages in thread
From: Martin Sebor @ 2016-09-20 20:15 UTC (permalink / raw)
  To: Jeff Law, Gcc Patch List
  Cc: Joseph Myers, Richard Biener, Jakub Jelinek, Bernd Schmidt,
	David Malcolm, Manuel López-Ibáñez,
	Florian Weimer

>> +  /* The minimum and maximum length.  The MAXLEN pointer stays unchanged
>> +     but MINLEN may be cleared during the execution of the function.  */
>> +  tree *minlen = length;
>> +  tree* const maxlen = length + 1;
> Check formatting here.  I'm not sure if the formatting standards
> explicitly state how to handle the "*" when there's a qualifier between
> the type and the object.  But please check.

If my regular expressions were right, most code puts the star right
before the const (379 occurrences in .c and .h files under the gcc/
directory not counting the test suite).  For example:

   gcc/cp/decl.c:  const char *const name;

There's quite a bit of code that also puts a space after it (219
occurrences).  For example:

   gcc/cgraph.h:extern const char * const tls_model_names[];

There are 28 occurrences of the style I used, mostly in function
declarations.  For instance:

gcc/cp/mangle.c:decl_is_template_id (const tree decl, tree* const 
template_info)

I changed my code to follow with the dominant style.

>
>> +        inform (info.fmtloc,
>> +            "using the range [%qE, %qE] for directive argument",
>> +            fmtres.argmin, fmtres.argmax);
> Don't you need G_ for these messages?
>
> Can you please check the calls to fmtwarn which pass in the string as an
> argument and add G_ as needed?  I think the cases where the string is
> computed into a variable are all handled correctly, it's jut those where
> the string appears as a call argument that need double checking.  I
> think there's ~3 of them.

I found an error() call in c-family/c-format.c with the string split
across two lines like here (see below) and the error message appears
correctly concatenated in the gcc.pot file so I take that to mean
that the G_ isn't necessary here.

   error ("found a %<%s%> reference but the format argument should"
	 " be a string", format_name (gcc_objc_string_format_type));

Other than that, though, since it's an easy mistake to make, it
would be helpful if problems like the one you were worried about
could be detected by some tool run during a build.  I wonder how
hard it would be to write an awk scirpt...

>> +#if 0
...
>> +#else
...
>> +#endif
> Please remove the #if 0'd code.

Sorry about that.  This was left over from my effort to persuade
the substring_loc class to underline just the last quote of the
format string.  I thought David had made some changes to make it
possible but I may have misremembered.  Let me raise a separate
bug for it.

> I think with the nits above fixed, this is OK for the trunk.
>
> Thanks for your patience.

Great, thank you!

Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-20 20:15                               ` Martin Sebor
@ 2016-09-20 20:33                                 ` David Malcolm
  2016-09-21  6:11                                   ` Martin Sebor
  0 siblings, 1 reply; 115+ messages in thread
From: David Malcolm @ 2016-09-20 20:33 UTC (permalink / raw)
  To: Martin Sebor, Jeff Law, Gcc Patch List
  Cc: Joseph Myers, Richard Biener, Jakub Jelinek, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

On Tue, 2016-09-20 at 13:35 -0600, Martin Sebor wrote:

[...]

> > > +#if 0
> ...
> > > +#else
> ...
> > > +#endif
> > Please remove the #if 0'd code.
> 
> Sorry about that.  This was left over from my effort to persuade
> the substring_loc class to underline just the last quote of the
> format string.  I thought David had made some changes to make it
> possible but I may have misremembered.  Let me raise a separate
> bug for it.

I don't think the substring_loc code supports such a feature yet;
please open a bug for it and CC me on it.

[...]

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-20 20:33                                 ` David Malcolm
@ 2016-09-21  6:11                                   ` Martin Sebor
  0 siblings, 0 replies; 115+ messages in thread
From: Martin Sebor @ 2016-09-21  6:11 UTC (permalink / raw)
  To: David Malcolm, Jeff Law, Gcc Patch List
  Cc: Joseph Myers, Richard Biener, Jakub Jelinek, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

On 09/20/2016 01:38 PM, David Malcolm wrote:
> On Tue, 2016-09-20 at 13:35 -0600, Martin Sebor wrote:
>
> [...]
>
>>>> +#if 0
>> ...
>>>> +#else
>> ...
>>>> +#endif
>>> Please remove the #if 0'd code.
>>
>> Sorry about that.  This was left over from my effort to persuade
>> the substring_loc class to underline just the last quote of the
>> format string.  I thought David had made some changes to make it
>> possible but I may have misremembered.  Let me raise a separate
>> bug for it.
>
> I don't think the substring_loc code supports such a feature yet;
> please open a bug for it and CC me on it.

Done in bug 77672 - wrong rich location in warning: writing
a terminating nul past the end.

Thanks
Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-12  8:07                           ` Martin Sebor
                                               ` (2 preceding siblings ...)
  2016-09-16 18:47                             ` David Malcolm
@ 2016-09-21  7:13                             ` Markus Trippelsdorf
  2016-09-21 10:57                               ` Christophe Lyon
                                                 ` (2 more replies)
  3 siblings, 3 replies; 115+ messages in thread
From: Markus Trippelsdorf @ 2016-09-21  7:13 UTC (permalink / raw)
  To: Martin Sebor
  Cc: Gcc Patch List, Joseph Myers, Jeff Law, Richard Biener,
	Jakub Jelinek, Bernd Schmidt, David Malcolm,
	Manuel López-Ibáñez, Florian Weimer

On 2016.09.11 at 20:03 -0600, Martin Sebor wrote:
> On 09/08/2016 04:10 PM, Joseph Myers wrote:
> > On Thu, 8 Sep 2016, Martin Sebor wrote:

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

The patch uses "nul" instead of "null" throughout.

-- 
Markus

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-21  7:13                             ` Markus Trippelsdorf
@ 2016-09-21 10:57                               ` Christophe Lyon
  2016-09-21 14:38                                 ` Martin Sebor
  2016-09-21 14:38                               ` Martin Sebor
  2016-09-21 15:40                               ` Jeff Law
  2 siblings, 1 reply; 115+ messages in thread
From: Christophe Lyon @ 2016-09-21 10:57 UTC (permalink / raw)
  To: Martin Sebor
  Cc: Gcc Patch List, Joseph Myers, Jeff Law, Richard Biener,
	Jakub Jelinek, Bernd Schmidt, David Malcolm,
	Manuel López-Ibáñez, Florian Weimer

Hi Martin,

On 21 September 2016 at 09:11, Markus Trippelsdorf
<markus@trippelsdorf.de> wrote:
> On 2016.09.11 at 20:03 -0600, Martin Sebor wrote:
>> On 09/08/2016 04:10 PM, Joseph Myers wrote:
>> > On Thu, 8 Sep 2016, Martin Sebor wrote:
>
>> PR middle-end/49905 - Better sanity checking on sprintf src & dest to
>>       produce warning for dodgy code
>
> The patch uses "nul" instead of "null" throughout.
>
> --
> Markus

I've noticed that some of the new tests fail on arm linux:
  gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 1220)
  gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 1270)
  gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 1381)
  gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 815)
  gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 816)
  gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 817)
  gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 820)
  gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 821)
  gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 824)
  gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 825)
  gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 828)
  gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 829)
  gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 832)
  gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 833)
  gcc.dg/tree-ssa/builtin-sprintf-warn-1.c (test for excess errors)
  gcc.dg/tree-ssa/builtin-sprintf-warn-3.c  (test for warnings, line 46)
  gcc.dg/tree-ssa/builtin-sprintf-warn-3.c  (test for warnings, line 47)
  gcc.dg/tree-ssa/builtin-sprintf-warn-3.c (test for excess errors)
  gcc.dg/tree-ssa/builtin-sprintf.c (test for excess errors)

on aarch64 and arm bare-metal targets (using newlib), I've noticed
these failures:
  gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 99)
  gcc.dg/tree-ssa/builtin-sprintf-warn-1.c (test for excess errors)
  gcc.dg/tree-ssa/builtin-sprintf-warn-2.c sprintf transformed into
strcpy (test for warnings, line 83)
  gcc.dg/tree-ssa/builtin-sprintf-warn-2.c sprintf transformed into
strcpy (test for warnings, line 84)
  gcc.dg/tree-ssa/builtin-sprintf-warn-4.c (test for excess errors)
  gcc.dg/tree-ssa/builtin-sprintf.c execution test

You may download the results from
http://people.linaro.org/~christophe.lyon/cross-validation/gcc/trunk/240298/report-build-info.html

Thanks,

Christophe

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-21  7:13                             ` Markus Trippelsdorf
  2016-09-21 10:57                               ` Christophe Lyon
@ 2016-09-21 14:38                               ` Martin Sebor
  2016-09-21 14:41                                 ` Alexander Monakov
  2016-09-21 15:40                               ` Jeff Law
  2 siblings, 1 reply; 115+ messages in thread
From: Martin Sebor @ 2016-09-21 14:38 UTC (permalink / raw)
  To: Markus Trippelsdorf
  Cc: Gcc Patch List, Joseph Myers, Jeff Law, Richard Biener,
	Jakub Jelinek, Bernd Schmidt, David Malcolm,
	Manuel López-Ibáñez, Florian Weimer

On 09/21/2016 01:11 AM, Markus Trippelsdorf wrote:
> On 2016.09.11 at 20:03 -0600, Martin Sebor wrote:
>> On 09/08/2016 04:10 PM, Joseph Myers wrote:
>>> On Thu, 8 Sep 2016, Martin Sebor wrote:
>
>> PR middle-end/49905 - Better sanity checking on sprintf src & dest to
>> 	produce warning for dodgy code
>
> The patch uses "nul" instead of "null" throughout.

Yes, that's intentional.  NUL and null are alternate spellings for
the same character. I went with nul to distinguish it from the null
pointer and used all lowercase as per the GCC convention.

Martin


^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-21 10:57                               ` Christophe Lyon
@ 2016-09-21 14:38                                 ` Martin Sebor
  0 siblings, 0 replies; 115+ messages in thread
From: Martin Sebor @ 2016-09-21 14:38 UTC (permalink / raw)
  To: Christophe Lyon
  Cc: Gcc Patch List, Joseph Myers, Jeff Law, Richard Biener,
	Jakub Jelinek, Bernd Schmidt, David Malcolm,
	Manuel López-Ibáñez, Florian Weimer

On 09/21/2016 04:36 AM, Christophe Lyon wrote:
> Hi Martin,
>
> On 21 September 2016 at 09:11, Markus Trippelsdorf
> <markus@trippelsdorf.de> wrote:
>> On 2016.09.11 at 20:03 -0600, Martin Sebor wrote:
>>> On 09/08/2016 04:10 PM, Joseph Myers wrote:
>>>> On Thu, 8 Sep 2016, Martin Sebor wrote:
>>
>>> PR middle-end/49905 - Better sanity checking on sprintf src & dest to
>>>       produce warning for dodgy code
>>
>> The patch uses "nul" instead of "null" throughout.
>>
>> --
>> Markus
>
> I've noticed that some of the new tests fail on arm linux:
>   gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 1220)
>   gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 1270)
>   gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 1381)
>   gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 815)
>   gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 816)
>   gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 817)
>   gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 820)
>   gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 821)
>   gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 824)
>   gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 825)
>   gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 828)
>   gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 829)
>   gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 832)
>   gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 833)
>   gcc.dg/tree-ssa/builtin-sprintf-warn-1.c (test for excess errors)
>   gcc.dg/tree-ssa/builtin-sprintf-warn-3.c  (test for warnings, line 46)
>   gcc.dg/tree-ssa/builtin-sprintf-warn-3.c  (test for warnings, line 47)
>   gcc.dg/tree-ssa/builtin-sprintf-warn-3.c (test for excess errors)
>   gcc.dg/tree-ssa/builtin-sprintf.c (test for excess errors)
>
> on aarch64 and arm bare-metal targets (using newlib), I've noticed
> these failures:
>   gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 99)
>   gcc.dg/tree-ssa/builtin-sprintf-warn-1.c (test for excess errors)
>   gcc.dg/tree-ssa/builtin-sprintf-warn-2.c sprintf transformed into
> strcpy (test for warnings, line 83)
>   gcc.dg/tree-ssa/builtin-sprintf-warn-2.c sprintf transformed into
> strcpy (test for warnings, line 84)
>   gcc.dg/tree-ssa/builtin-sprintf-warn-4.c (test for excess errors)
>   gcc.dg/tree-ssa/builtin-sprintf.c execution test
>
> You may download the results from
> http://people.linaro.org/~christophe.lyon/cross-validation/gcc/trunk/240298/report-build-info.html

Thanks, I'll look into these today.

Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-21 14:38                               ` Martin Sebor
@ 2016-09-21 14:41                                 ` Alexander Monakov
  2016-09-21 15:04                                   ` Szabolcs Nagy
  2016-09-21 15:24                                   ` Martin Sebor
  0 siblings, 2 replies; 115+ messages in thread
From: Alexander Monakov @ 2016-09-21 14:41 UTC (permalink / raw)
  To: Martin Sebor
  Cc: Markus Trippelsdorf, Gcc Patch List, Joseph Myers, Jeff Law,
	Richard Biener, Jakub Jelinek, Bernd Schmidt, David Malcolm,
	Manuel López-Ibáñez, Florian Weimer

On Wed, 21 Sep 2016, Martin Sebor wrote:
> On 09/21/2016 01:11 AM, Markus Trippelsdorf wrote:
> > The patch uses "nul" instead of "null" throughout.
> 
> Yes, that's intentional.  NUL and null are alternate spellings for
> the same character. I went with nul to distinguish it from the null
> pointer and used all lowercase as per the GCC convention.

Can you elaborate which guideline suggests spelling that in lowercase?
It seems quite strange to me, especially given that the documentation
added with the patch uses "NUL character" (which I believe to be a more
common form), but then warnings use "nul" (without the "character" iiuc).

Thanks.
Alexander

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-21 14:41                                 ` Alexander Monakov
@ 2016-09-21 15:04                                   ` Szabolcs Nagy
  2016-09-21 18:04                                     ` Pedro Alves
  2016-09-21 15:24                                   ` Martin Sebor
  1 sibling, 1 reply; 115+ messages in thread
From: Szabolcs Nagy @ 2016-09-21 15:04 UTC (permalink / raw)
  To: Alexander Monakov, Martin Sebor
  Cc: nd, Markus Trippelsdorf, Gcc Patch List, Joseph Myers, Jeff Law,
	Richard Biener, Jakub Jelinek, Bernd Schmidt, David Malcolm,
	Manuel López-Ibáñez, Florian Weimer

On 21/09/16 15:37, Alexander Monakov wrote:
> On Wed, 21 Sep 2016, Martin Sebor wrote:
>> On 09/21/2016 01:11 AM, Markus Trippelsdorf wrote:
>>> The patch uses "nul" instead of "null" throughout.
>>
>> Yes, that's intentional.  NUL and null are alternate spellings for
>> the same character. I went with nul to distinguish it from the null
>> pointer and used all lowercase as per the GCC convention.
> 
> Can you elaborate which guideline suggests spelling that in lowercase?

the c standard calls it "null character".

> It seems quite strange to me, especially given that the documentation
> added with the patch uses "NUL character" (which I believe to be a more
> common form), but then warnings use "nul" (without the "character" iiuc).
> 
> Thanks.
> Alexander
> 

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-21 14:41                                 ` Alexander Monakov
  2016-09-21 15:04                                   ` Szabolcs Nagy
@ 2016-09-21 15:24                                   ` Martin Sebor
  1 sibling, 0 replies; 115+ messages in thread
From: Martin Sebor @ 2016-09-21 15:24 UTC (permalink / raw)
  To: Alexander Monakov
  Cc: Markus Trippelsdorf, Gcc Patch List, Joseph Myers, Jeff Law,
	Richard Biener, Jakub Jelinek, Bernd Schmidt, David Malcolm,
	Manuel López-Ibáñez, Florian Weimer

On 09/21/2016 08:37 AM, Alexander Monakov wrote:
> On Wed, 21 Sep 2016, Martin Sebor wrote:
>> On 09/21/2016 01:11 AM, Markus Trippelsdorf wrote:
>>> The patch uses "nul" instead of "null" throughout.
>>
>> Yes, that's intentional.  NUL and null are alternate spellings for
>> the same character. I went with nul to distinguish it from the null
>> pointer and used all lowercase as per the GCC convention.
>
> Can you elaborate which guideline suggests spelling that in lowercase?
> It seems quite strange to me, especially given that the documentation
> added with the patch uses "NUL character" (which I believe to be a more
> common form), but then warnings use "nul" (without the "character" iiuc).

I don't know if there is a specific guideline that applies here,
but it's my impression that conventionally GCC messages use
lowercase letters.  The only guideline I know about is in the GNU
Coding Standard that calls for messages not to begin with capital
letters (or end in a period;  though now that I've checked more
carefully there are a fair number of messages that ignore that
suggestion).

That said, if the lowercase nul is offensive it can certainly be
changed to some other spelling (as can any of the other messages
emitted by the pass).

Martin

PS Yes, the C and C++ standards refer to NUL as the null character.
NUL is an abbreviation, used extensively (for example) by POSIX.

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-21  7:13                             ` Markus Trippelsdorf
  2016-09-21 10:57                               ` Christophe Lyon
  2016-09-21 14:38                               ` Martin Sebor
@ 2016-09-21 15:40                               ` Jeff Law
  2 siblings, 0 replies; 115+ messages in thread
From: Jeff Law @ 2016-09-21 15:40 UTC (permalink / raw)
  To: Markus Trippelsdorf, Martin Sebor
  Cc: Gcc Patch List, Joseph Myers, Richard Biener, Jakub Jelinek,
	Bernd Schmidt, David Malcolm, Manuel López-Ibáñez,
	Florian Weimer

On 09/21/2016 01:11 AM, Markus Trippelsdorf wrote:
> On 2016.09.11 at 20:03 -0600, Martin Sebor wrote:
>> On 09/08/2016 04:10 PM, Joseph Myers wrote:
>>> On Thu, 8 Sep 2016, Martin Sebor wrote:
>
>> PR middle-end/49905 - Better sanity checking on sprintf src & dest to
>> 	produce warning for dodgy code
>
> The patch uses "nul" instead of "null" throughout.
It was referring to a character rather than a null pointer.  That's why 
I didn't call it out.

jeff

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-21 15:04                                   ` Szabolcs Nagy
@ 2016-09-21 18:04                                     ` Pedro Alves
  0 siblings, 0 replies; 115+ messages in thread
From: Pedro Alves @ 2016-09-21 18:04 UTC (permalink / raw)
  To: Szabolcs Nagy, Alexander Monakov, Martin Sebor
  Cc: nd, Markus Trippelsdorf, Gcc Patch List, Joseph Myers, Jeff Law,
	Richard Biener, Jakub Jelinek, Bernd Schmidt, David Malcolm,
	Manuel López-Ibáñez, Florian Weimer

On 09/21/2016 03:59 PM, Szabolcs Nagy wrote:
> On 21/09/16 15:37, Alexander Monakov wrote:
>> On Wed, 21 Sep 2016, Martin Sebor wrote:
>>> On 09/21/2016 01:11 AM, Markus Trippelsdorf wrote:
>>>> The patch uses "nul" instead of "null" throughout.
>>>
>>> Yes, that's intentional.  NUL and null are alternate spellings for
>>> the same character. I went with nul to distinguish it from the null
>>> pointer and used all lowercase as per the GCC convention.
>>
>> Can you elaborate which guideline suggests spelling that in lowercase?
> 
> the c standard calls it "null character".

*nod*  

The character is called "NUL" just much as I'm called "palves".  :-)

I.e., three-letter uppercase "NUL" is a control character
name abbreviation, just like, ACK, LF, DEL, ESC, etc.

The original ASCII standard used "NULL" for abbreviation, and then
in later revisions, it was shortened to "NUL".  All control
characters have 2 or 3 letter abbreviations in the latest ASCII
standard, and are written in uppercase, which to me looks like
the obvious reason for "NUL" with single "L".

https://en.wikipedia.org/wiki/Control_character
https://en.wikipedia.org/wiki/ASCII

https://web.archive.org/web/20160605145632/http://worldpowersystems.com/archives/codes/X3.4-1963/page5.JPG
https://web.archive.org/web/20160613203624/https://tools.ietf.org/html/rfc20

From the latter:

~~~
[...]
5.2 Control Characters

      NUL (Null): The all-zeros character which may serve to accomplish
   time fill and media fill.
      SOH (Start of Heading): A communication control character used at
   the beginning of a sequence of characters which constitute a
[...]
~~~

> 
>> It seems quite strange to me, especially given that the documentation
>> added with the patch uses "NUL character" (which I believe to be a more
>> common form), but then warnings use "nul" (without the "character" iiuc).
>>

To me too.  Lowercase "nul" has all the looks of a typo to me.
I'd only use lowercase like that if abbreviations of other characters
were lowercase too in similar contexts, like "... the esc character ...",
etc.

Thanks,
Pedro Alves

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-20 18:27                               ` Martin Sebor
@ 2016-09-21 19:46                                 ` Gerald Pfeifer
  2016-09-21 20:00                                   ` Martin Sebor
  2016-09-22  9:11                                 ` Rainer Orth
  1 sibling, 1 reply; 115+ messages in thread
From: Gerald Pfeifer @ 2016-09-21 19:46 UTC (permalink / raw)
  To: Martin Sebor
  Cc: David Malcolm, gcc-patches, Joseph Myers, Jeff Law,
	Richard Biener, Jakub Jelinek, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

[-- Attachment #1: Type: text/plain, Size: 1061 bytes --]

I noticed the following bootstrap failure on i?86-unknown-freebsd
that started in the last 24 hours:

/scratch/tmp/gerald/gcc-HEAD/gcc/vec.c: In member function ‘void vec_usage::dump(mem_location*, mem_usage&) const’:
/scratch/tmp/gerald/gcc-HEAD/gcc/vec.c:79:3: error: ‘%s’ directive writing between 0 and 4294967295 bytes into a region of size 4096 [-Werror=format-length=]
   dump (mem_location *loc, mem_usage &total) const
   ^~~~
/scratch/tmp/gerald/gcc-HEAD/gcc/vec.c:83:36: note: format output between 6 and4294967311 bytes into a destination of size 4096
       loc->m_line, loc->m_function);
                                    ^
cc1plus: all warnings being treated as errors
gmake[3]: *** [Makefile:2557: build/vec.o] Error 1
gmake[3]: Leaving directory '/scratch/tmp/gerald/OBJ-0921-1705/gcc'
gmake[2]: *** [Makefile:4612: all-stage2-gcc] Error 2
gmake[2]: Leaving directory '/scratch/tmp/gerald/OBJ-0921-1705'
gmake[1]: *** [Makefile:24365: stage2-bubble] Error 2

Is it possible that is related to your warning patches?

Gerald

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-21 19:46                                 ` Gerald Pfeifer
@ 2016-09-21 20:00                                   ` Martin Sebor
  2016-09-21 22:06                                     ` Jakub Jelinek
  2016-09-21 22:10                                     ` Gerald Pfeifer
  0 siblings, 2 replies; 115+ messages in thread
From: Martin Sebor @ 2016-09-21 20:00 UTC (permalink / raw)
  To: Gerald Pfeifer
  Cc: David Malcolm, gcc-patches, Joseph Myers, Jeff Law,
	Richard Biener, Jakub Jelinek, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

On 09/21/2016 01:40 PM, Gerald Pfeifer wrote:
> I noticed the following bootstrap failure on i?86-unknown-freebsd
> that started in the last 24 hours:
>
> /scratch/tmp/gerald/gcc-HEAD/gcc/vec.c: In member function ‘void vec_usage::dump(mem_location*, mem_usage&) const’:
> /scratch/tmp/gerald/gcc-HEAD/gcc/vec.c:79:3: error: ‘%s’ directive writing between 0 and 4294967295 bytes into a region of size 4096 [-Werror=format-length=]
>    dump (mem_location *loc, mem_usage &total) const
>    ^~~~
> /scratch/tmp/gerald/gcc-HEAD/gcc/vec.c:83:36: note: format output between 6 and4294967311 bytes into a destination of size 4096
>        loc->m_line, loc->m_function);
>                                     ^
> cc1plus: all warnings being treated as errors
> gmake[3]: *** [Makefile:2557: build/vec.o] Error 1
> gmake[3]: Leaving directory '/scratch/tmp/gerald/OBJ-0921-1705/gcc'
> gmake[2]: *** [Makefile:4612: all-stage2-gcc] Error 2
> gmake[2]: Leaving directory '/scratch/tmp/gerald/OBJ-0921-1705'
> gmake[1]: *** [Makefile:24365: stage2-bubble] Error 2
>
> Is it possible that is related to your warning patches?

Yes, this is likely the same bug as mentioned in comment #6 on
pr77676.  The bug in the comment ILP32-specific and only tangentially
related to the PR itself.  I'm testing the patch that's attached to
the PR that should fix both of these problems.  I don't have access
to i?86-unknown-freebsd so if you could help validate it there I'd
be grateful.  (The patch just successfully bootstrapped on
i386-pc-linux-gnu.)

Thanks and sorry for the breakage.
Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-21 20:00                                   ` Martin Sebor
@ 2016-09-21 22:06                                     ` Jakub Jelinek
  2016-09-21 22:10                                     ` Gerald Pfeifer
  1 sibling, 0 replies; 115+ messages in thread
From: Jakub Jelinek @ 2016-09-21 22:06 UTC (permalink / raw)
  To: Martin Sebor
  Cc: Gerald Pfeifer, David Malcolm, gcc-patches, Joseph Myers,
	Jeff Law, Richard Biener, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

On Wed, Sep 21, 2016 at 01:55:33PM -0600, Martin Sebor wrote:
> On 09/21/2016 01:40 PM, Gerald Pfeifer wrote:
> >I noticed the following bootstrap failure on i?86-unknown-freebsd
> >that started in the last 24 hours:
> >
> >/scratch/tmp/gerald/gcc-HEAD/gcc/vec.c: In member function ‘void vec_usage::dump(mem_location*, mem_usage&) const’:
> >/scratch/tmp/gerald/gcc-HEAD/gcc/vec.c:79:3: error: ‘%s’ directive writing between 0 and 4294967295 bytes into a region of size 4096 [-Werror=format-length=]
> >   dump (mem_location *loc, mem_usage &total) const
> >   ^~~~
> >/scratch/tmp/gerald/gcc-HEAD/gcc/vec.c:83:36: note: format output between 6 and4294967311 bytes into a destination of size 4096
> >       loc->m_line, loc->m_function);
> >                                    ^
> >cc1plus: all warnings being treated as errors
> >gmake[3]: *** [Makefile:2557: build/vec.o] Error 1
> >gmake[3]: Leaving directory '/scratch/tmp/gerald/OBJ-0921-1705/gcc'
> >gmake[2]: *** [Makefile:4612: all-stage2-gcc] Error 2
> >gmake[2]: Leaving directory '/scratch/tmp/gerald/OBJ-0921-1705'
> >gmake[1]: *** [Makefile:24365: stage2-bubble] Error 2
> >
> >Is it possible that is related to your warning patches?
> 
> Yes, this is likely the same bug as mentioned in comment #6 on
> pr77676.  The bug in the comment ILP32-specific and only tangentially
> related to the PR itself.  I'm testing the patch that's attached to
> the PR that should fix both of these problems.  I don't have access
> to i?86-unknown-freebsd so if you could help validate it there I'd
> be grateful.  (The patch just successfully bootstrapped on
> i386-pc-linux-gnu.)

Looking at target_int_max you are using in the new patch:
static unsigned HOST_WIDE_INT
target_int_max ()
{
  static const unsigned HOST_WIDE_INT int_max
    = HOST_WIDE_INT_M1U >> (sizeof int_max * CHAR_BIT
                            - TYPE_PRECISION (integer_type_node) + 1);
  return int_max;
}

1) sizeof int_max * CHAR_BIT should IMNSHO be HOST_BITS_PER_WIDE_INT
2) why is the var static, subtraction and shift is very cheap, while C++
   local statics are expensive?  It needs a guard variable,
   __cxa_guard_acquire, __cxa_guard_release calls, etc.

	Jakub

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-21 20:00                                   ` Martin Sebor
  2016-09-21 22:06                                     ` Jakub Jelinek
@ 2016-09-21 22:10                                     ` Gerald Pfeifer
  1 sibling, 0 replies; 115+ messages in thread
From: Gerald Pfeifer @ 2016-09-21 22:10 UTC (permalink / raw)
  To: Martin Sebor
  Cc: David Malcolm, gcc-patches, Joseph Myers, Jeff Law,
	Richard Biener, Jakub Jelinek, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

On Wed, 21 Sep 2016, Martin Sebor wrote:
>> Is it possible that is related to your warning patches?
> Yes, this is likely the same bug as mentioned in comment #6 on
> pr77676.  The bug in the comment ILP32-specific and only tangentially
> related to the PR itself.  I'm testing the patch that's attached to
> the PR that should fix both of these problems.  I don't have access
> to i?86-unknown-freebsd so if you could help validate it there I'd
> be grateful.

Yes, with this patch I was able to bootstrap i?86-unknown-freebsd11
again.

Thanks for looking into this so quickly, Martin!

Gerald

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-20 18:27                               ` Martin Sebor
  2016-09-21 19:46                                 ` Gerald Pfeifer
@ 2016-09-22  9:11                                 ` Rainer Orth
  2016-09-22 12:53                                   ` Rainer Orth
  2016-09-22 15:04                                   ` Martin Sebor
  1 sibling, 2 replies; 115+ messages in thread
From: Rainer Orth @ 2016-09-22  9:11 UTC (permalink / raw)
  To: Martin Sebor
  Cc: David Malcolm, Gcc Patch List, Joseph Myers, Jeff Law,
	Richard Biener, Jakub Jelinek, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

[-- Attachment #1: Type: text/plain, Size: 2089 bytes --]

Hi Martin,

>> Another nit, if I may: FWIW I'm not in love with the wording of the
>> messages.  Sorry to bikeshed, but how about:
>>   warning: buffer overflow will occur when writing terminating NUL
>> and:
>>   note: formatted output of 2 bytes into a destination of size 1
>> or somesuch.
>
> I won't claim the text of the messages is perfect but I did spend
> a lot of time tweaking them.  That's not to say they can't be
> improved but changing them means quite a bit of work adjusting
> the tests.  At this point, I'd like to focus on getting the patch
> committed.  After that, if there's still time, I'm happy to take
> feedback and tweak the diagnostics based on it.
>
> Thanks again for your help and the suggestions above!

your patch broke bootstrap with MPFR 2.4.2, which is still the
recommended (or perhaps minimal) version according to install.texi:

/vol/gcc/src/hg/trunk/local/gcc/gimple-ssa-sprintf.c: In function 'int {anonymous}::format_floating_max(tree, char)':
/vol/gcc/src/hg/trunk/local/gcc/gimple-ssa-sprintf.c:1128:27: error: 'MPFR_RNDN' was not declared in this scope
   mpfr_from_real (x, &rv, MPFR_RNDN);
                           ^
/vol/gcc/src/hg/trunk/local/gcc/gimple-ssa-sprintf.c: In function '{anonymous}::fmtresult {anonymous}::format_floating(const {anonymous}::conversion_spec&, tree)':
/vol/gcc/src/hg/trunk/local/gcc/gimple-ssa-sprintf.c:1328:37: error: 'MPFR_RNDN' was not declared in this scope
       mpfr_from_real (mpfrval, rvp, MPFR_RNDN);
                                     ^

MPFR_RNDN was only introduced in mpfr 3.0.0, and everywhere else in gcc
GMP_RNDN is used instead.  mpfr 3.0.0 <mpfr.h> has 

/* kept for compatibility with MPFR 2.4.x and before */
#define GMP_RNDN MPFR_RNDN

The following patch (together with your other one to fix ILP32 targets)
allows a sparc-sun-solaris2.12 bootstrap to continue.  I'm going to
commit it as obvious.

	Rainer


2016-09-22  Rainer Orth  <ro@CeBiTec.Uni-Bielefeld.DE>

	gcc:
	* gimple-ssa-sprintf.c (format_floating_max): Use GMP_RNDN instead
	of MPFR_RNDN.
	(format_floating): Likewise.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: mpfr_rndn.patch --]
[-- Type: text/x-patch, Size: 741 bytes --]

diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c
--- a/gcc/gimple-ssa-sprintf.c
+++ b/gcc/gimple-ssa-sprintf.c
@@ -1125,7 +1127,7 @@ format_floating_max (tree type, char spe
      round-to-nearest mode.  */
   mpfr_t x;
   mpfr_init2 (x, rfmt->p);
-  mpfr_from_real (x, &rv, MPFR_RNDN);
+  mpfr_from_real (x, &rv, GMP_RNDN);
 
   const char fmt[] = { '%', 'R', spec, '\0' };
   int n = mpfr_snprintf (NULL, 0, fmt, x);
@@ -1325,7 +1327,7 @@ format_floating (const conversion_spec &
 	 round-to-nearest mode.  */
       mpfr_t mpfrval;
       mpfr_init2 (mpfrval, rfmt->p);
-      mpfr_from_real (mpfrval, rvp, MPFR_RNDN);
+      mpfr_from_real (mpfrval, rvp, GMP_RNDN);
 
       char fmtstr [40];
       char *pfmt = fmtstr;

[-- Attachment #3: Type: text/plain, Size: 143 bytes --]


-- 
-----------------------------------------------------------------------------
Rainer Orth, Center for Biotechnology, Bielefeld University

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-22  9:11                                 ` Rainer Orth
@ 2016-09-22 12:53                                   ` Rainer Orth
  2016-09-22 15:11                                     ` Martin Sebor
  2016-09-22 15:04                                   ` Martin Sebor
  1 sibling, 1 reply; 115+ messages in thread
From: Rainer Orth @ 2016-09-22 12:53 UTC (permalink / raw)
  To: Martin Sebor
  Cc: David Malcolm, Gcc Patch List, Joseph Myers, Jeff Law,
	Richard Biener, Jakub Jelinek, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

Hi Martin,

> your patch broke bootstrap with MPFR 2.4.2, which is still the
> recommended (or perhaps minimal) version according to install.texi:
[...]
> The following patch (together with your other one to fix ILP32 targets)
> allows a sparc-sun-solaris2.12 bootstrap to continue.  I'm going to
> commit it as obvious.

done now.  Once the bootstrap had finished, I see quite a number of
testsuite failures (i386-pc-solaris2.12 still running), both 32 and
64-bit:

+FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 1220)
+FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 1270)
+FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 1381)
+FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 356)
+FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 99)
+FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c (test for excess errors)

Excess errors:
/vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:209:3: warning: format '%lc' expects argument of type 'wint_t', but argument 5 has type 'int' [-Wformat=]
/vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:210:3: warning: format '%lc' expects argument of type 'wint_t', but argument 5 has type 'int' [-Wformat=]
/vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:211:3: warning: format '%lc' expects argument of type 'wint_t', but argument 5 has type 'int' [-Wformat=]
/vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:211:3: warning: format '%lc' expects argument of type 'wint_t', but argument 6 has type 'int' [-Wformat=]
/vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:212:3: warning: format '%lc' expects argument of type 'wint_t', but argument 5 has type 'int' [-Wformat=]
/vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:213:3: warning: format '%lc' expects argument of type 'wint_t', but argument 5 has type 'int' [-Wformat=]
/vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:213:3: warning: format '%lc' expects argument of type 'wint_t', but argument 6 has type 'int' [-Wformat=]
/vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:1238:3: warning: format '%lc' expects argument of type 'wint_t', but argument 4 has type 'int' [-Wformat=]
/vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:1239:3: warning: format '%lc' expects argument of type 'wint_t', but argument 4 has type 'int' [-Wformat=]
/vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:1240:3: warning: format '%lc' expects argument of type 'wint_t', but argument 4 has type 'int' [-Wformat=]
/vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:1287:3: warning: format '%lc' expects argument of type 'wint_t', but argument 6 has type 'int' [-Wformat=]
/vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:1288:3: warning: format '%lc' expects argument of type 'wint_t', but argument 6 has type 'int' [-Wformat=]
/vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:1289:3: warning: format '%lc' expects argument of type 'wint_t', but argument 6 has type 'int' [-Wformat=]
/vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:99:3: warning: '%p' directive writing 1 byte into a region of size 0 [-Wformat-length=]
/vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:1406:3: warning: specified size 4294967295 exceeds the size 2 of the destination object [-Wformat-length=]

+FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-2.c  (test for warnings, line 50)
+FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-2.c sprintf transformed into strcpy 
(test for warnings, line 83)
+FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-2.c sprintf transformed into strcpy 
(test for warnings, line 84)

+FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-4.c (test for excess errors)

Excess errors:
/vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c:10:21: warning: writing a terminating nul past the end of the destination [-Wformat-length=]/vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c:22:22: warning: '%-s' directive writing 4 bytes into a region of size 1 [-Wformat-length=]

+FAIL: gcc.dg/tree-ssa/builtin-sprintf.c execution test

FAIL: test_a_double:364: "%a" expected result for "0x0.0000000000000p+0" doesn't match function call return value: 20 != 6
FAIL: test_a_double:365: "%a" expected result for "0x1.0000000000000p+0" doesn't match function call return value: 20 != 6
FAIL: test_a_double:366: "%a" expected result for "0x1.0000000000000p+1" doesn't match function call return value: 20 != 6

FAIL: test_a_long_double:375: "%La" expected result for "0x0.0000000000000000000000000000p+0" doesn't match function call return value: 35 != 6
FAIL: test_a_long_double:376: "%La" expected result for "0x1.0000000000000000000000000000p+0" doesn't match function call return value: 35 != 6
FAIL: test_a_long_double:377: "%La" expected result for "0x1.0000000000000000000000000000p+1" doesn't match function call return value: 35 != 6

	Rainer

-- 
-----------------------------------------------------------------------------
Rainer Orth, Center for Biotechnology, Bielefeld University

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-22  9:11                                 ` Rainer Orth
  2016-09-22 12:53                                   ` Rainer Orth
@ 2016-09-22 15:04                                   ` Martin Sebor
  1 sibling, 0 replies; 115+ messages in thread
From: Martin Sebor @ 2016-09-22 15:04 UTC (permalink / raw)
  To: Rainer Orth
  Cc: David Malcolm, Gcc Patch List, Joseph Myers, Jeff Law,
	Richard Biener, Jakub Jelinek, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

> your patch broke bootstrap with MPFR 2.4.2, which is still the
> recommended (or perhaps minimal) version according to install.texi:
>
> /vol/gcc/src/hg/trunk/local/gcc/gimple-ssa-sprintf.c: In function 'int {anonymous}::format_floating_max(tree, char)':
> /vol/gcc/src/hg/trunk/local/gcc/gimple-ssa-sprintf.c:1128:27: error: 'MPFR_RNDN' was not declared in this scope
>    mpfr_from_real (x, &rv, MPFR_RNDN);
>                            ^
> /vol/gcc/src/hg/trunk/local/gcc/gimple-ssa-sprintf.c: In function '{anonymous}::fmtresult {anonymous}::format_floating(const {anonymous}::conversion_spec&, tree)':
> /vol/gcc/src/hg/trunk/local/gcc/gimple-ssa-sprintf.c:1328:37: error: 'MPFR_RNDN' was not declared in this scope
>        mpfr_from_real (mpfrval, rvp, MPFR_RNDN);
>                                      ^
>
> MPFR_RNDN was only introduced in mpfr 3.0.0, and everywhere else in gcc
> GMP_RNDN is used instead.  mpfr 3.0.0 <mpfr.h> has
>
> /* kept for compatibility with MPFR 2.4.x and before */
> #define GMP_RNDN MPFR_RNDN
>
> The following patch (together with your other one to fix ILP32 targets)
> allows a sparc-sun-solaris2.12 bootstrap to continue.  I'm going to
> commit it as obvious.

Thanks.  I didn't realize the older MPFR was still recommended.
FWIW, I was using MPFR 2.4 until recently but after running into
some (unexplained) runtime issues I reran the download_prerequisites
script to update them and it installed 3.1.4.

It seems that either the recommended MPFR version should be bumped
up (or the script downgraded to 2.4 to avoid this risk, though I
suspect there are reasons why it was updated to the latest).

Based on the log the script was updated from MPFR 2.4 to 3.0 in
r235763.  The change also updated install.texi but not the MPFR
version mentioned there.  It might be worth checking with the
author, Bernd Edlinger, to see how to resolve this.  Let me ping
him in a separate thread.

Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-22 12:53                                   ` Rainer Orth
@ 2016-09-22 15:11                                     ` Martin Sebor
  2016-09-23 14:07                                       ` Rainer Orth
  2016-09-24 18:14                                       ` Andreas Schwab
  0 siblings, 2 replies; 115+ messages in thread
From: Martin Sebor @ 2016-09-22 15:11 UTC (permalink / raw)
  To: Rainer Orth
  Cc: David Malcolm, Gcc Patch List, Joseph Myers, Jeff Law,
	Richard Biener, Jakub Jelinek, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

On 09/22/2016 06:14 AM, Rainer Orth wrote:
> Hi Martin,
>
>> your patch broke bootstrap with MPFR 2.4.2, which is still the
>> recommended (or perhaps minimal) version according to install.texi:
> [...]
>> The following patch (together with your other one to fix ILP32 targets)
>> allows a sparc-sun-solaris2.12 bootstrap to continue.  I'm going to
>> commit it as obvious.
>
> done now.  Once the bootstrap had finished, I see quite a number of
> testsuite failures (i386-pc-solaris2.12 still running), both 32 and
> 64-bit:
>
> +FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 1220)
> +FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 1270)
> +FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 1381)
> +FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 356)
> +FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 99)
> +FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c (test for excess errors)

I have a patch for (hopefully) most of these failures that I will
commit along with the one for pr77676 as soon as it's approved.

> Excess errors:
> /vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:209:3: warning: format '%lc' expects argument of type 'wint_t', but argument 5 has type 'int' [-Wformat=]
> /vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:210:3: warning: format '%lc' expects argument of type 'wint_t', but argument 5 has type 'int' [-Wformat=]
> /vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:211:3: warning: format '%lc' expects argument of type 'wint_t', but argument 5 has type 'int' [-Wformat=]
> /vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:211:3: warning: format '%lc' expects argument of type 'wint_t', but argument 6 has type 'int' [-Wformat=]
> /vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:212:3: warning: format '%lc' expects argument of type 'wint_t', but argument 5 has type 'int' [-Wformat=]
> /vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:213:3: warning: format '%lc' expects argument of type 'wint_t', but argument 5 has type 'int' [-Wformat=]
> /vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:213:3: warning: format '%lc' expects argument of type 'wint_t', but argument 6 has type 'int' [-Wformat=]
> /vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:1238:3: warning: format '%lc' expects argument of type 'wint_t', but argument 4 has type 'int' [-Wformat=]
> /vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:1239:3: warning: format '%lc' expects argument of type 'wint_t', but argument 4 has type 'int' [-Wformat=]
> /vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:1240:3: warning: format '%lc' expects argument of type 'wint_t', but argument 4 has type 'int' [-Wformat=]
> /vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:1287:3: warning: format '%lc' expects argument of type 'wint_t', but argument 6 has type 'int' [-Wformat=]
> /vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:1288:3: warning: format '%lc' expects argument of type 'wint_t', but argument 6 has type 'int' [-Wformat=]
> /vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:1289:3: warning: format '%lc' expects argument of type 'wint_t', but argument 6 has type 'int' [-Wformat=]
> /vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:99:3: warning: '%p' directive writing 1 byte into a region of size 0 [-Wformat-length=]
> /vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:1406:3: warning: specified size 4294967295 exceeds the size 2 of the destination object [-Wformat-length=]
>
> +FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-2.c  (test for warnings, line 50)
> +FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-2.c sprintf transformed into strcpy
> (test for warnings, line 83)
> +FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-2.c sprintf transformed into strcpy
> (test for warnings, line 84)
>
> +FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-4.c (test for excess errors)
>
> Excess errors:
> /vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c:10:21: warning: writing a terminating nul past the end of the destination [-Wformat-length=]/vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c:22:22: warning: '%-s' directive writing 4 bytes into a region of size 1 [-Wformat-length=]
>
> +FAIL: gcc.dg/tree-ssa/builtin-sprintf.c execution test
>
> FAIL: test_a_double:364: "%a" expected result for "0x0.0000000000000p+0" doesn't match function call return value: 20 != 6
> FAIL: test_a_double:365: "%a" expected result for "0x1.0000000000000p+0" doesn't match function call return value: 20 != 6
> FAIL: test_a_double:366: "%a" expected result for "0x1.0000000000000p+1" doesn't match function call return value: 20 != 6
>
> FAIL: test_a_long_double:375: "%La" expected result for "0x0.0000000000000000000000000000p+0" doesn't match function call return value: 35 != 6
> FAIL: test_a_long_double:376: "%La" expected result for "0x1.0000000000000000000000000000p+0" doesn't match function call return value: 35 != 6
> FAIL: test_a_long_double:377: "%La" expected result for "0x1.0000000000000000000000000000p+1" doesn't match function call return value: 35 != 6

I don't know about these.  It looks like the Solaris printf doesn't
handle the %a directive correctly and the tests (and the related
checks/optimization) might need to be disabled, which in turn might
involve extending the existing printf hook or adding a new one.
I don't have access to Solaris to fully debug and test this there.
Would you mind helping with it?

Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-22 15:11                                     ` Martin Sebor
@ 2016-09-23 14:07                                       ` Rainer Orth
  2016-10-03 18:05                                         ` Martin Sebor
  2016-09-24 18:14                                       ` Andreas Schwab
  1 sibling, 1 reply; 115+ messages in thread
From: Rainer Orth @ 2016-09-23 14:07 UTC (permalink / raw)
  To: Martin Sebor
  Cc: David Malcolm, Gcc Patch List, Joseph Myers, Jeff Law,
	Richard Biener, Jakub Jelinek, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

Hi Martin,

> On 09/22/2016 06:14 AM, Rainer Orth wrote:
>> Hi Martin,
>>
>>> your patch broke bootstrap with MPFR 2.4.2, which is still the
>>> recommended (or perhaps minimal) version according to install.texi:
>> [...]
>>> The following patch (together with your other one to fix ILP32 targets)
>>> allows a sparc-sun-solaris2.12 bootstrap to continue.  I'm going to
>>> commit it as obvious.
>>
>> done now.  Once the bootstrap had finished, I see quite a number of
>> testsuite failures (i386-pc-solaris2.12 still running), both 32 and
>> 64-bit:
>>
>> +FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c (test for warnings, line
>> 1220)
>> +FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c (test for warnings, line
>> 1270)
>> +FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c (test for warnings, line
>> 1381)
>> +FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 356)
>> +FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 99)
>> +FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c (test for excess errors)
>
> I have a patch for (hopefully) most of these failures that I will
> commit along with the one for pr77676 as soon as it's approved.

as of r240389, a few of those failures occur also on x86_64-pc-linux-gnu
with -m32:

FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 1222)
FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 1272)
FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 1383)
FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c (test for excess errors)

>> +FAIL: gcc.dg/tree-ssa/builtin-sprintf.c execution test
>>
>> FAIL: test_a_double:364: "%a" expected result for "0x0.0000000000000p+0"
>> doesn't match function call return value: 20 != 6
>> FAIL: test_a_double:365: "%a" expected result for "0x1.0000000000000p+0"
>> doesn't match function call return value: 20 != 6
>> FAIL: test_a_double:366: "%a" expected result for "0x1.0000000000000p+1"
>> doesn't match function call return value: 20 != 6
>>
>> FAIL: test_a_long_double:375: "%La" expected result for
>> "0x0.0000000000000000000000000000p+0" doesn't match function call return
>> value: 35 != 6
>> FAIL: test_a_long_double:376: "%La" expected result for
>> "0x1.0000000000000000000000000000p+0" doesn't match function call return
>> value: 35 != 6
>> FAIL: test_a_long_double:377: "%La" expected result for
>> "0x1.0000000000000000000000000000p+1" doesn't match function call return
>> value: 35 != 6
>
> I don't know about these.  It looks like the Solaris printf doesn't
> handle the %a directive correctly and the tests (and the related
> checks/optimization) might need to be disabled, which in turn might
> involve extending the existing printf hook or adding a new one.

I've found the following in Solaris 10 (and up) printf(3C):

     a, A    A  double  argument  representing  a  floating-point
             number  is  converted in the style "[-]0xh.hhhhp+d",
             where the single  hexadecimal  digit  preceding  the
             radix  point is 0 if the value converted is zero and
             1 otherwise and the  number  of  hexadecimal  digits
             after it is equal to the precision; if the precision
             is missing, the number of digits printed  after  the
             radix  point  is  13  for the conversion of a double
             value, 16 for the conversion of a long double  value
             on  x86,  and 28 for the conversion of a long double
             value on SPARC; if the precision is zero and the '#'
             flag  is  not  specified, no decimal-point character
             will appear. The letters "abcdef"  are  used  for  a
             conversion  and  the  letters "ABCDEF" for A conver-
             sion. The A conversion specifier produces  a  number
             with  'X'  and  'P'  instead  of  'x'  and  'p'. The
             exponent will always contain at least one digit, and
             only  as  many more digits as necessary to represent
             the decimal exponent of 2. If the value is zero, the
             exponent is zero.

             The converted value is rounded to fit the  specified
             output  format  according to the prevailing floating
             point rounding direction mode. If the conversion  is
             not exact, an inexact exception is raised.

             A double argument representing an infinity or NaN is
             converted in the SUSv3 style of an e or E conversion
             specifier.

I tried to check the relevant sections of the latest C99 and C11 drafts
to check if this handling of missing precision is allowed by the
standard, but I couldn't even fully parse the language there.

> I don't have access to Solaris to fully debug and test this there.
> Would you mind helping with it?

Not at all: if it turns out that Solaris has bugs in this area, I can
easily file them, too.

Thanks.
        Rainer

-- 
-----------------------------------------------------------------------------
Rainer Orth, Center for Biotechnology, Bielefeld University

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-22 15:11                                     ` Martin Sebor
  2016-09-23 14:07                                       ` Rainer Orth
@ 2016-09-24 18:14                                       ` Andreas Schwab
  2016-09-28 19:03                                         ` Martin Sebor
  1 sibling, 1 reply; 115+ messages in thread
From: Andreas Schwab @ 2016-09-24 18:14 UTC (permalink / raw)
  To: Martin Sebor
  Cc: Rainer Orth, David Malcolm, Gcc Patch List, Joseph Myers,
	Jeff Law, Richard Biener, Jakub Jelinek, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

I'm still seeing these failures on m68k:

FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 358)
FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 1222)
FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 1272)
FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 1383)
FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c (test for excess errors)
Excess errors:
/daten/aranym/gcc/gcc-20160924/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:1408:3: warning: specified size 4294967295 exceeds the size 2 of the destination object [-Wformat-length=]

Andreas.

-- 
Andreas Schwab, schwab@linux-m68k.org
GPG Key fingerprint = 58CA 54C7 6D53 942B 1756  01D3 44D5 214B 8276 4ED5
"And now for something completely different."

^ permalink raw reply	[flat|nested] 115+ messages in thread

* [BUILDROBOT] tic6x-uclinux: undefined reference to `gnu_libc_printf_pointer_format(tree_node*, char const**)' (was: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905))
  2016-09-08 19:19                       ` Martin Sebor
                                           ` (2 preceding siblings ...)
  2016-09-16 17:07                         ` Jeff Law
@ 2016-09-27  0:17                         ` Jan-Benedict Glaw
  2016-09-27 17:52                           ` [BUILDROBOT] tic6x-uclinux: undefined reference to `gnu_libc_printf_pointer_format(tree_node*, char const**)' Martin Sebor
  2016-09-28 12:41                           ` [BUILDROBOT] tic6x-uclinux: undefined reference to `gnu_libc_printf_pointer_format(tree_node*, char const**)' (was: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)) Segher Boessenkool
  3 siblings, 2 replies; 115+ messages in thread
From: Jan-Benedict Glaw @ 2016-09-27  0:17 UTC (permalink / raw)
  To: Martin Sebor; +Cc: Gcc Patch List

[-- Attachment #1: Type: text/plain, Size: 2236 bytes --]

Hi Martin,

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

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

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


Add another one for uClibc?

MfG, JBG

-- 
      Jan-Benedict Glaw      jbglaw@lug-owl.de              +49-172-7608481
  Signature of:                          Zensur im Internet? Nein danke!
  the second  :

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [BUILDROBOT] tic6x-uclinux: undefined reference to `gnu_libc_printf_pointer_format(tree_node*, char const**)'
  2016-09-27  0:17                         ` [BUILDROBOT] tic6x-uclinux: undefined reference to `gnu_libc_printf_pointer_format(tree_node*, char const**)' (was: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)) Jan-Benedict Glaw
@ 2016-09-27 17:52                           ` Martin Sebor
  2016-09-28 12:41                           ` [BUILDROBOT] tic6x-uclinux: undefined reference to `gnu_libc_printf_pointer_format(tree_node*, char const**)' (was: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)) Segher Boessenkool
  1 sibling, 0 replies; 115+ messages in thread
From: Martin Sebor @ 2016-09-27 17:52 UTC (permalink / raw)
  To: Jan-Benedict Glaw; +Cc: Gcc Patch List

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

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

Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [BUILDROBOT] tic6x-uclinux: undefined reference to `gnu_libc_printf_pointer_format(tree_node*, char const**)' (was: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905))
  2016-09-27  0:17                         ` [BUILDROBOT] tic6x-uclinux: undefined reference to `gnu_libc_printf_pointer_format(tree_node*, char const**)' (was: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)) Jan-Benedict Glaw
  2016-09-27 17:52                           ` [BUILDROBOT] tic6x-uclinux: undefined reference to `gnu_libc_printf_pointer_format(tree_node*, char const**)' Martin Sebor
@ 2016-09-28 12:41                           ` Segher Boessenkool
  1 sibling, 0 replies; 115+ messages in thread
From: Segher Boessenkool @ 2016-09-28 12:41 UTC (permalink / raw)
  To: Jan-Benedict Glaw; +Cc: Martin Sebor, Gcc Patch List

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

[ snip ]

The same happens for bfin-uclinux:

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

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


Segher

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-24 18:14                                       ` Andreas Schwab
@ 2016-09-28 19:03                                         ` Martin Sebor
  0 siblings, 0 replies; 115+ messages in thread
From: Martin Sebor @ 2016-09-28 19:03 UTC (permalink / raw)
  To: Andreas Schwab
  Cc: Rainer Orth, David Malcolm, Gcc Patch List, Joseph Myers,
	Jeff Law, Richard Biener, Jakub Jelinek, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

On 09/24/2016 10:39 AM, Andreas Schwab wrote:
> I'm still seeing these failures on m68k:
>
> FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 358)
> FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 1222)
> FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 1272)
> FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c  (test for warnings, line 1383)
> FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c (test for excess errors)
> Excess errors:
> /daten/aranym/gcc/gcc-20160924/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:1408:3: warning: specified size 4294967295 exceeds the size 2 of the destination object [-Wformat-length=]

I'm pretty sure the first two are due to the same problem as bug 77735.
I plan to look into fixing it today or later this week.  The last two
were caused by bug v77762 and should be fixed by now.

Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-09-23 14:07                                       ` Rainer Orth
@ 2016-10-03 18:05                                         ` Martin Sebor
  2016-10-04  9:29                                           ` Rainer Orth
  0 siblings, 1 reply; 115+ messages in thread
From: Martin Sebor @ 2016-10-03 18:05 UTC (permalink / raw)
  To: Rainer Orth
  Cc: David Malcolm, Gcc Patch List, Joseph Myers, Jeff Law,
	Richard Biener, Jakub Jelinek, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

>>> +FAIL: gcc.dg/tree-ssa/builtin-sprintf.c execution test
>>>
>>> FAIL: test_a_double:364: "%a" expected result for "0x0.0000000000000p+0"
>>> doesn't match function call return value: 20 != 6
>>> FAIL: test_a_double:365: "%a" expected result for "0x1.0000000000000p+0"
>>> doesn't match function call return value: 20 != 6
>>> FAIL: test_a_double:366: "%a" expected result for "0x1.0000000000000p+1"
>>> doesn't match function call return value: 20 != 6
>>>
>>> FAIL: test_a_long_double:375: "%La" expected result for
>>> "0x0.0000000000000000000000000000p+0" doesn't match function call return
>>> value: 35 != 6
>>> FAIL: test_a_long_double:376: "%La" expected result for
>>> "0x1.0000000000000000000000000000p+0" doesn't match function call return
>>> value: 35 != 6
>>> FAIL: test_a_long_double:377: "%La" expected result for
>>> "0x1.0000000000000000000000000000p+1" doesn't match function call return
>>> value: 35 != 6
>>
>> I don't know about these.  It looks like the Solaris printf doesn't
>> handle the %a directive correctly and the tests (and the related
>> checks/optimization) might need to be disabled, which in turn might
>> involve extending the existing printf hook or adding a new one.
>
> I've found the following in Solaris 10 (and up) printf(3C):
>
>      a, A    A  double  argument  representing  a  floating-point
>              number  is  converted in the style "[-]0xh.hhhhp+d",
>              where the single  hexadecimal  digit  preceding  the
>              radix  point is 0 if the value converted is zero and
>              1 otherwise and the  number  of  hexadecimal  digits
>              after it is equal to the precision; if the precision
>              is missing, the number of digits printed  after  the
>              radix  point  is  13  for the conversion of a double
>              value, 16 for the conversion of a long double  value
>              on  x86,  and 28 for the conversion of a long double
>              value on SPARC; if the precision is zero and the '#'
>              flag  is  not  specified, no decimal-point character
>              will appear. The letters "abcdef"  are  used  for  a
>              conversion  and  the  letters "ABCDEF" for A conver-
>              sion. The A conversion specifier produces  a  number
>              with  'X'  and  'P'  instead  of  'x'  and  'p'. The
>              exponent will always contain at least one digit, and
>              only  as  many more digits as necessary to represent
>              the decimal exponent of 2. If the value is zero, the
>              exponent is zero.
>
>              The converted value is rounded to fit the  specified
>              output  format  according to the prevailing floating
>              point rounding direction mode. If the conversion  is
>              not exact, an inexact exception is raised.
>
>              A double argument representing an infinity or NaN is
>              converted in the SUSv3 style of an e or E conversion
>              specifier.
>
> I tried to check the relevant sections of the latest C99 and C11 drafts
> to check if this handling of missing precision is allowed by the
> standard, but I couldn't even fully parse the language there.
>
>> I don't have access to Solaris to fully debug and test this there.
>> Would you mind helping with it?
>
> Not at all: if it turns out that Solaris has bugs in this area, I can
> easily file them, too.

I think it's actually a defect in the C standard.  It doesn't
specify how many decimal digits an implementation must produce
on output for a plain %a directive (i.e., when precision isn't
explicitly specified).  With Glibc, for instance, printf("%a",
1.0) prints 0x8p-3 while on Solaris it prints  0x8.000000p-3.
Both seem reasonable but neither is actually specified.  In
theory, an implementation is allowed print any number of zeros
after the decimal point, which the standard should (IMO) not
permit.  There should be a cap (e.g., of at most 6 decimal
digits when precision is not specified with %a, just like
there us with %e).  I'll propose to change the standard and
forward it to the C committee.  Until then, I've worked
around it in the patch for pr77735 (under review).  If you
have a moment and could try it out on Solaris and let me
know how it goes I'd be grateful.

Thanks
Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-10-03 18:05                                         ` Martin Sebor
@ 2016-10-04  9:29                                           ` Rainer Orth
  2016-10-04 23:30                                             ` Martin Sebor
  0 siblings, 1 reply; 115+ messages in thread
From: Rainer Orth @ 2016-10-04  9:29 UTC (permalink / raw)
  To: Martin Sebor
  Cc: David Malcolm, Gcc Patch List, Joseph Myers, Jeff Law,
	Richard Biener, Jakub Jelinek, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

Hi Martin,

>>>> +FAIL: gcc.dg/tree-ssa/builtin-sprintf.c execution test
>>>>
>>>> FAIL: test_a_double:364: "%a" expected result for "0x0.0000000000000p+0"
>>>> doesn't match function call return value: 20 != 6
>>>> FAIL: test_a_double:365: "%a" expected result for "0x1.0000000000000p+0"
>>>> doesn't match function call return value: 20 != 6
>>>> FAIL: test_a_double:366: "%a" expected result for "0x1.0000000000000p+1"
>>>> doesn't match function call return value: 20 != 6
>>>>
>>>> FAIL: test_a_long_double:375: "%La" expected result for
>>>> "0x0.0000000000000000000000000000p+0" doesn't match function call return
>>>> value: 35 != 6
>>>> FAIL: test_a_long_double:376: "%La" expected result for
>>>> "0x1.0000000000000000000000000000p+0" doesn't match function call return
>>>> value: 35 != 6
>>>> FAIL: test_a_long_double:377: "%La" expected result for
>>>> "0x1.0000000000000000000000000000p+1" doesn't match function call return
>>>> value: 35 != 6
>>>
>>> I don't know about these.  It looks like the Solaris printf doesn't
>>> handle the %a directive correctly and the tests (and the related
>>> checks/optimization) might need to be disabled, which in turn might
>>> involve extending the existing printf hook or adding a new one.
>>
>> I've found the following in Solaris 10 (and up) printf(3C):
>>
>>      a, A    A  double  argument  representing  a  floating-point
>>              number  is  converted in the style "[-]0xh.hhhhp+d",
>>              where the single  hexadecimal  digit  preceding  the
>>              radix  point is 0 if the value converted is zero and
>>              1 otherwise and the  number  of  hexadecimal  digits
>>              after it is equal to the precision; if the precision
>>              is missing, the number of digits printed  after  the
>>              radix  point  is  13  for the conversion of a double
>>              value, 16 for the conversion of a long double  value
>>              on  x86,  and 28 for the conversion of a long double
>>              value on SPARC; if the precision is zero and the '#'
>>              flag  is  not  specified, no decimal-point character
>>              will appear. The letters "abcdef"  are  used  for  a
>>              conversion  and  the  letters "ABCDEF" for A conver-
>>              sion. The A conversion specifier produces  a  number
>>              with  'X'  and  'P'  instead  of  'x'  and  'p'. The
>>              exponent will always contain at least one digit, and
>>              only  as  many more digits as necessary to represent
>>              the decimal exponent of 2. If the value is zero, the
>>              exponent is zero.
>>
>>              The converted value is rounded to fit the  specified
>>              output  format  according to the prevailing floating
>>              point rounding direction mode. If the conversion  is
>>              not exact, an inexact exception is raised.
>>
>>              A double argument representing an infinity or NaN is
>>              converted in the SUSv3 style of an e or E conversion
>>              specifier.
>>
>> I tried to check the relevant sections of the latest C99 and C11 drafts
>> to check if this handling of missing precision is allowed by the
>> standard, but I couldn't even fully parse the language there.
>>
>>> I don't have access to Solaris to fully debug and test this there.
>>> Would you mind helping with it?
>>
>> Not at all: if it turns out that Solaris has bugs in this area, I can
>> easily file them, too.
>
> I think it's actually a defect in the C standard.  It doesn't
> specify how many decimal digits an implementation must produce
> on output for a plain %a directive (i.e., when precision isn't
> explicitly specified).  With Glibc, for instance, printf("%a",
> 1.0) prints 0x8p-3 while on Solaris it prints  0x8.000000p-3.
> Both seem reasonable but neither is actually specified.  In
> theory, an implementation is allowed print any number of zeros
> after the decimal point, which the standard should (IMO) not
> permit.  There should be a cap (e.g., of at most 6 decimal
> digits when precision is not specified with %a, just like
> there us with %e).  I'll propose to change the standard and
> forward it to the C committee.  Until then, I've worked
> around it in the patch for pr77735 (under review).  If you
> have a moment and could try it out on Solaris and let me
> know how it goes I'd be grateful.

as it happens, I'd already started bootstraps with your patch before
your mail arrived :-)

We're left with

FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c (test for excess errors)
FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-4.c (test for excess errors)

for 32 bit and

FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-4.c (test for excess errors)

for 64 bit on both i386-pc-solaris2.12 and sparc-sun-solaris2.12.

In the 32-bit builtin-sprintf-warn-1.c case, there are many instances of

Excess errors:
/vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:224:3: warning: format '%lc' expects argument of type 'wint_t', but argument 5 has type 'int' [-Wformat=]

while the second is

Excess errors:
/vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c:15:23: warning: writing a terminating nul past the end of the destination [-Wformat-length=]/vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c:30:21: warning: writing format character '4' at offset 3 past the end of the destination [-Wformat-length=]
/vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c:46:21: warning: writing format character '4' at offset 3 past the end of the destination [-Wformat-length=]
/vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c:61:25: warning: writing a terminating nul past the end of the destination [-Wformat-length=]
/vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c:74:22: warning: '%-s' directive writing 4 bytes into a region of size 1 [-Wformat-length=]

I've no idea yet why in the first error message two different messages
are joined into one line.  Probably something with DejaGnu mangling the
output...

	Rainer

-- 
-----------------------------------------------------------------------------
Rainer Orth, Center for Biotechnology, Bielefeld University

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-10-04  9:29                                           ` Rainer Orth
@ 2016-10-04 23:30                                             ` Martin Sebor
  2016-10-05  0:22                                               ` Joseph Myers
  2016-10-13  9:23                                               ` Rainer Orth
  0 siblings, 2 replies; 115+ messages in thread
From: Martin Sebor @ 2016-10-04 23:30 UTC (permalink / raw)
  To: Rainer Orth
  Cc: David Malcolm, Gcc Patch List, Joseph Myers, Jeff Law,
	Richard Biener, Jakub Jelinek, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

> as it happens, I'd already started bootstraps with your patch before
> your mail arrived :-)

Thanks for your help getting to the bottom of this!

>
> We're left with
>
> FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c (test for excess errors)
> FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-4.c (test for excess errors)
>
> for 32 bit and
>
> FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-4.c (test for excess errors)
>
> for 64 bit on both i386-pc-solaris2.12 and sparc-sun-solaris2.12.
>
> In the 32-bit builtin-sprintf-warn-1.c case, there are many instances of
>
> Excess errors:
> /vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:224:3: warning: format '%lc' expects argument of type 'wint_t', but argument 5 has type 'int' [-Wformat=]

I've built the sparc-sun-solaris2.12 toolchain and reproduced these
warnings.  They are vestiges of those I saw and some of which I fixed
before.  The problem is that %lc expects a wint_t argument which on
this target is an alias for long in but the argument of 0 has type
int.  The warning is coming out of the -Wformat checker which doesn't
seem to care that int and long have the same size.  I've committed
r240758 that should fix the remaining warnings of this kind but long
term I think GCC should change to avoid warning in this case (Clang
doesn't).

>
> while the second is
>
> Excess errors:
> /vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c:15:23: warning: writing a terminating nul past the end of the destination [-Wformat-length=]/vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c:30:21: warning: writing format character '4' at offset 3 past the end of the destination [-Wformat-length=]
> /vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c:46:21: warning: writing format character '4' at offset 3 past the end of the destination [-Wformat-length=]
> /vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c:61:25: warning: writing a terminating nul past the end of the destination [-Wformat-length=]
> /vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c:74:22: warning: '%-s' directive writing 4 bytes into a region of size 1 [-Wformat-length=]
>
> I've no idea yet why in the first error message two different messages
> are joined into one line.  Probably something with DejaGnu mangling the
> output...

I've reproduced this as well and it took me a while to see the
problem.  It turns out that the target specifier I used in the
test (*-*-*-*) happened to match my native target
x86_64-pc-linux-gnu but not sparc-sun-solaris2.12.  Let me fix
that in the next patch.  Hopefully with that all the remaining
failures should clear up.

Thanks again for your help and patience!

Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-10-04 23:30                                             ` Martin Sebor
@ 2016-10-05  0:22                                               ` Joseph Myers
  2016-10-05  0:31                                                 ` Martin Sebor
  2016-10-13  9:23                                               ` Rainer Orth
  1 sibling, 1 reply; 115+ messages in thread
From: Joseph Myers @ 2016-10-05  0:22 UTC (permalink / raw)
  To: Martin Sebor
  Cc: Rainer Orth, David Malcolm, Gcc Patch List, Jeff Law,
	Richard Biener, Jakub Jelinek, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

On Tue, 4 Oct 2016, Martin Sebor wrote:

> I've built the sparc-sun-solaris2.12 toolchain and reproduced these
> warnings.  They are vestiges of those I saw and some of which I fixed
> before.  The problem is that %lc expects a wint_t argument which on
> this target is an alias for long in but the argument of 0 has type
> int.  The warning is coming out of the -Wformat checker which doesn't
> seem to care that int and long have the same size.  I've committed
> r240758 that should fix the remaining warnings of this kind but long
> term I think GCC should change to avoid warning in this case (Clang
> doesn't).

Well, typically cases where one of long and int is passed and the other is 
expected, but they have the same size, are bugs waiting to happen when the 
code is built on a 64-bit system.  That is, they *should* warn.

There have been arguments that we should go further and warn for e.g. %zu 
with a type that happens to be the same as size_t but doesn't use the 
size_t typedef (or sizeof etc.), %td for something that happens to be the 
same as ptrdiff_t but doesn't use the typedef (or pointer difference 
etc.), etc. - which would get many similar cases of bugs waiting to happen 
on a different system, but is also tricker because you need to decide 
whether a given type is logically size_t etc. or not - code could validly 
use autoconf to identify the underlying type, or use __SIZE_TYPE__, or use 
an expression mixing size_t with other types, or in the case of %j* 
(intmax_t) use the INTMAX_C macro to construct constants.  That probably 
*would* need an option to disable just those format warnings (whereas I 
don't see the need for such an option for the case of mixing int and 
long).

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-10-05  0:22                                               ` Joseph Myers
@ 2016-10-05  0:31                                                 ` Martin Sebor
  2016-10-05 15:40                                                   ` Joseph Myers
  0 siblings, 1 reply; 115+ messages in thread
From: Martin Sebor @ 2016-10-05  0:31 UTC (permalink / raw)
  To: Joseph Myers
  Cc: Rainer Orth, David Malcolm, Gcc Patch List, Jeff Law,
	Richard Biener, Jakub Jelinek, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

On 10/04/2016 06:21 PM, Joseph Myers wrote:
> On Tue, 4 Oct 2016, Martin Sebor wrote:
>
>> I've built the sparc-sun-solaris2.12 toolchain and reproduced these
>> warnings.  They are vestiges of those I saw and some of which I fixed
>> before.  The problem is that %lc expects a wint_t argument which on
>> this target is an alias for long in but the argument of 0 has type
>> int.  The warning is coming out of the -Wformat checker which doesn't
>> seem to care that int and long have the same size.  I've committed
>> r240758 that should fix the remaining warnings of this kind but long
>> term I think GCC should change to avoid warning in this case (Clang
>> doesn't).
>
> Well, typically cases where one of long and int is passed and the other is
> expected, but they have the same size, are bugs waiting to happen when the
> code is built on a 64-bit system.  That is, they *should* warn.

Typically, yes.  In the case of wchar_t (or int) and wint_t I don't
think it's helpful.  Consider this case from my comment #5 on bug
72858.  I don't think there is any point in issuing a warning here.
On the majority of targets they either all are or promote to a type
of the same size, don't they?

$ cat t.c && gcc -S -Wall -m32 t.c
typedef __WCHAR_TYPE__ wchar_t;

void f (const wchar_t *ws)
{
   __builtin_printf ("%lc", *ws);
}
t.c: In function ‘f’:
t.c:5:24: warning: format ‘%lc’ expects argument of type ‘wint_t’, but 
argument 2 has type ‘wchar_t {aka const long int}’ [-Wformat=]
    __builtin_printf ("%lc", *ws);
                       ~~^   ~~~
                       %ld
>
> There have been arguments that we should go further and warn for e.g. %zu
> with a type that happens to be the same as size_t but doesn't use the
> size_t typedef (or sizeof etc.), %td for something that happens to be the
> same as ptrdiff_t but doesn't use the typedef (or pointer difference
> etc.), etc. - which would get many similar cases of bugs waiting to happen
> on a different system, but is also tricker because you need to decide
> whether a given type is logically size_t etc. or not - code could validly
> use autoconf to identify the underlying type, or use __SIZE_TYPE__, or use
> an expression mixing size_t with other types, or in the case of %j*
> (intmax_t) use the INTMAX_C macro to construct constants.  That probably
> *would* need an option to disable just those format warnings (whereas I
> don't see the need for such an option for the case of mixing int and
> long).

I would view it useful if GCC warned on %zu with an argument that's
not size_t, and similarly for other directives and typedefs, for the
reason you mention.  But I don't think the same rationale applies
to warning on %lc with wchar_t or int arguments.

Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-10-05  0:31                                                 ` Martin Sebor
@ 2016-10-05 15:40                                                   ` Joseph Myers
  2016-10-05 15:45                                                     ` Jakub Jelinek
  2016-10-05 16:20                                                     ` Martin Sebor
  0 siblings, 2 replies; 115+ messages in thread
From: Joseph Myers @ 2016-10-05 15:40 UTC (permalink / raw)
  To: Martin Sebor
  Cc: Rainer Orth, David Malcolm, Gcc Patch List, Jeff Law,
	Richard Biener, Jakub Jelinek, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

On Tue, 4 Oct 2016, Martin Sebor wrote:

> > Well, typically cases where one of long and int is passed and the other is
> > expected, but they have the same size, are bugs waiting to happen when the
> > code is built on a 64-bit system.  That is, they *should* warn.
> 
> Typically, yes.  In the case of wchar_t (or int) and wint_t I don't
> think it's helpful.  Consider this case from my comment #5 on bug
> 72858.  I don't think there is any point in issuing a warning here.
> On the majority of targets they either all are or promote to a type
> of the same size, don't they?

I'm unconvinced by that "majority of targets" of argument (which once 
would have been true for int and long, and you could say it is true for 
long and size_t).  There's a clear correct type here, and it's wint_t, and 
it's always easy to fix the code to use the correct type.

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-10-05 15:40                                                   ` Joseph Myers
@ 2016-10-05 15:45                                                     ` Jakub Jelinek
  2016-10-05 16:11                                                       ` Joseph Myers
  2016-10-05 16:20                                                     ` Martin Sebor
  1 sibling, 1 reply; 115+ messages in thread
From: Jakub Jelinek @ 2016-10-05 15:45 UTC (permalink / raw)
  To: Joseph Myers
  Cc: Martin Sebor, Rainer Orth, David Malcolm, Gcc Patch List,
	Jeff Law, Richard Biener, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

On Wed, Oct 05, 2016 at 03:40:18PM +0000, Joseph Myers wrote:
> On Tue, 4 Oct 2016, Martin Sebor wrote:
> 
> > > Well, typically cases where one of long and int is passed and the other is
> > > expected, but they have the same size, are bugs waiting to happen when the
> > > code is built on a 64-bit system.  That is, they *should* warn.
> > 
> > Typically, yes.  In the case of wchar_t (or int) and wint_t I don't
> > think it's helpful.  Consider this case from my comment #5 on bug
> > 72858.  I don't think there is any point in issuing a warning here.
> > On the majority of targets they either all are or promote to a type
> > of the same size, don't they?
> 
> I'm unconvinced by that "majority of targets" of argument (which once 
> would have been true for int and long, and you could say it is true for 
> long and size_t).  There's a clear correct type here, and it's wint_t, and 
> it's always easy to fix the code to use the correct type.

But, can we reliably detect differences between wint_t and unsigned int if
wint_t is a typedef to that, or size_t and unsigned long etc., I mean doesn't
early folding already throw it away?

	Jakub

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-10-05 15:45                                                     ` Jakub Jelinek
@ 2016-10-05 16:11                                                       ` Joseph Myers
  0 siblings, 0 replies; 115+ messages in thread
From: Joseph Myers @ 2016-10-05 16:11 UTC (permalink / raw)
  To: Jakub Jelinek
  Cc: Martin Sebor, Rainer Orth, David Malcolm, Gcc Patch List,
	Jeff Law, Richard Biener, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

On Wed, 5 Oct 2016, Jakub Jelinek wrote:

> On Wed, Oct 05, 2016 at 03:40:18PM +0000, Joseph Myers wrote:
> > On Tue, 4 Oct 2016, Martin Sebor wrote:
> > 
> > > > Well, typically cases where one of long and int is passed and the other is
> > > > expected, but they have the same size, are bugs waiting to happen when the
> > > > code is built on a 64-bit system.  That is, they *should* warn.
> > > 
> > > Typically, yes.  In the case of wchar_t (or int) and wint_t I don't
> > > think it's helpful.  Consider this case from my comment #5 on bug
> > > 72858.  I don't think there is any point in issuing a warning here.
> > > On the majority of targets they either all are or promote to a type
> > > of the same size, don't they?
> > 
> > I'm unconvinced by that "majority of targets" of argument (which once 
> > would have been true for int and long, and you could say it is true for 
> > long and size_t).  There's a clear correct type here, and it's wint_t, and 
> > it's always easy to fix the code to use the correct type.
> 
> But, can we reliably detect differences between wint_t and unsigned int if
> wint_t is a typedef to that, or size_t and unsigned long etc., I mean doesn't
> early folding already throw it away?

Detecting it in all cases is hard as for size_t etc., but that shouldn't 
stop us warning in the cases where the types are different enough that we 
can tell the program is wrong.

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-10-05 15:40                                                   ` Joseph Myers
  2016-10-05 15:45                                                     ` Jakub Jelinek
@ 2016-10-05 16:20                                                     ` Martin Sebor
  2016-10-05 16:27                                                       ` Joseph Myers
  1 sibling, 1 reply; 115+ messages in thread
From: Martin Sebor @ 2016-10-05 16:20 UTC (permalink / raw)
  To: Joseph Myers
  Cc: Rainer Orth, David Malcolm, Gcc Patch List, Jeff Law,
	Richard Biener, Jakub Jelinek, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

On 10/05/2016 09:40 AM, Joseph Myers wrote:
> On Tue, 4 Oct 2016, Martin Sebor wrote:
>
>>> Well, typically cases where one of long and int is passed and the other is
>>> expected, but they have the same size, are bugs waiting to happen when the
>>> code is built on a 64-bit system.  That is, they *should* warn.
>>
>> Typically, yes.  In the case of wchar_t (or int) and wint_t I don't
>> think it's helpful.  Consider this case from my comment #5 on bug
>> 72858.  I don't think there is any point in issuing a warning here.
>> On the majority of targets they either all are or promote to a type
>> of the same size, don't they?
>
> I'm unconvinced by that "majority of targets" of argument (which once
> would have been true for int and long, and you could say it is true for
> long and size_t).  There's a clear correct type here, and it's wint_t, and
> it's always easy to fix the code to use the correct type.

The warning would only helpful if there was a target where wchar_t
didn't promote to a type with the same precision as wint_t, or in
other words where printf("%lc", L'a') did something different than
printf("%lc", (wint_t) L'a'). I don't know of such a target.  Can
you give an example of one?

It isn't any of the ILP32 targets where wint_t is int and wchar_t
is long and where the warning was found to cause test suite failures,
and it isn't any other other targets where the warning didn't trigger
because the two types have the same precision.

Otherwise, when there is no such target there is nothing to fix, and
changing working code just for the sake of pedantry only runs the
unnecessary risk of introducing bugs.  IMO, this is especially true
in subtle cases like this where the people who make the changes often
don't fully appreciate the subtleties of the code.  Suggesting the
wrong directive (replacing %lc with %ld) compounds the problem
further(*).

Martin

[*] This is not meant as a dig at David but rather to underscore
that both the "problem" is subtle and the expected solution not
obvious.

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-10-05 16:20                                                     ` Martin Sebor
@ 2016-10-05 16:27                                                       ` Joseph Myers
  2016-10-05 17:12                                                         ` Martin Sebor
  0 siblings, 1 reply; 115+ messages in thread
From: Joseph Myers @ 2016-10-05 16:27 UTC (permalink / raw)
  To: Martin Sebor
  Cc: Rainer Orth, David Malcolm, Gcc Patch List, Jeff Law,
	Richard Biener, Jakub Jelinek, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

On Wed, 5 Oct 2016, Martin Sebor wrote:

> The warning would only helpful if there was a target where wchar_t
> didn't promote to a type with the same precision as wint_t, or in

Or it could show up a logical error where the code should have had a 
wint_t variable and checked for WEOF somewhere but just assumed things 
would fit in wchar_t (and so could be passing a negative value where the 
ABI means the callee expects wint_t values to be zero-extended, with 
consequent undefined behavior there).  The correct fix involves thinking 
about where you know something is a wide character, and where something 
might be a wide character or WEOF.  It might then involve a cast to 
wint_t, or changing the type of a variable, or more changes than that.

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-10-05 16:27                                                       ` Joseph Myers
@ 2016-10-05 17:12                                                         ` Martin Sebor
  2016-10-05 17:30                                                           ` Joseph Myers
  0 siblings, 1 reply; 115+ messages in thread
From: Martin Sebor @ 2016-10-05 17:12 UTC (permalink / raw)
  To: Joseph Myers
  Cc: Rainer Orth, David Malcolm, Gcc Patch List, Jeff Law,
	Richard Biener, Jakub Jelinek, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

On 10/05/2016 10:27 AM, Joseph Myers wrote:
> On Wed, 5 Oct 2016, Martin Sebor wrote:
>
>> The warning would only helpful if there was a target where wchar_t
>> didn't promote to a type with the same precision as wint_t, or in
>
> Or it could show up a logical error where the code should have had a
> wint_t variable and checked for WEOF somewhere but just assumed things
> would fit in wchar_t

WEOF that fits in a wint_t also fits in a wchar_t when the two types
have the same precision, the case where GCC issues the warning (and
where WEOF expands to 0xffffffffu).  So this isn't an example of
a problem that could arise on the targets that brought this up, or
any other targets that I'm aware of.

> (and so could be passing a negative value where the
> ABI means the callee expects wint_t values to be zero-extended, with
> consequent undefined behavior there).

Sign extension is also not an issue on the target where the warning
is issued (x86_64 ILP32) and where wchar_t is int and wint_t is
unsigned long.

> The correct fix involves thinking
> about where you know something is a wide character, and where something
> might be a wide character or WEOF.  It might then involve a cast to
> wint_t, or changing the type of a variable, or more changes than that.

There is nothing to fix when wchar_t and wint_t have the same
precision and  printf("%lc", L'x') does the same thing as
printf("%lc", (wint_t) L'x').  The cast is entirely superfluous.

Even so, the warning could have merit if there were a different
target to which the program might need to be ported where this
property didn't hold.  But as I said, I don't know of one and,
as it seems, neither do you.

I'm very much in favor of strict warnings that help point out
either real or potential bugs.  But I see no value in warning
about constructs that are perfectly safe.  To the contrary,
I think such warnings are harmful because they lead to
unnecessary churn and to bug injection.

It might be worthwhile to mention that Clang used to issue the
same warning as GCC until 2011 when they fixed it.  The bug is
here:

   https://llvm.org/bugs/show_bug.cgi?id=7981

(Although I think there is a flaw in the logic they used in
comment #2 it's only hypothetical because there is no target
that Clang supports where the submitted test case doesn't do
what it's expected to do either.)

Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-10-05 17:12                                                         ` Martin Sebor
@ 2016-10-05 17:30                                                           ` Joseph Myers
  2016-10-05 22:59                                                             ` Martin Sebor
  0 siblings, 1 reply; 115+ messages in thread
From: Joseph Myers @ 2016-10-05 17:30 UTC (permalink / raw)
  To: Martin Sebor
  Cc: Rainer Orth, David Malcolm, Gcc Patch List, Jeff Law,
	Richard Biener, Jakub Jelinek, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

On Wed, 5 Oct 2016, Martin Sebor wrote:

> > Or it could show up a logical error where the code should have had a
> > wint_t variable and checked for WEOF somewhere but just assumed things
> > would fit in wchar_t
> 
> WEOF that fits in a wint_t also fits in a wchar_t when the two types
> have the same precision, the case where GCC issues the warning (and
> where WEOF expands to 0xffffffffu).  So this isn't an example of
> a problem that could arise on the targets that brought this up, or
> any other targets that I'm aware of.

The problem is e.g. code calling getwchar and not checking for WEOF.  
0xffffffffu does not fit in a 32-bit signed variable unless your code is 
confusing signed and unsigned freely.  And even if you consider it OK to 
check after conversion to wchar_t rather than before, the lack of a wint_t 
variable for the result seems likely to accompany a missing check.

> > (and so could be passing a negative value where the
> > ABI means the callee expects wint_t values to be zero-extended, with
> > consequent undefined behavior there).
> 
> Sign extension is also not an issue on the target where the warning
> is issued (x86_64 ILP32) and where wchar_t is int and wint_t is
> unsigned long.

There are plenty of architectures where the ABI does require the high part 
to be extended in a particular way; that's why the Linux kernel has 
syscall wrappers, because improperly extended 32-bit syscall arguments 
caused security issues in the past.  In some cases it's even 
architecturally undefined what the processor does when an instruction 
input isn't properly extended (although at least in the MIPS case the 
required extension there is always sign-extension, whether the 32-bit 
value is considered signed or unsigned).

I'd actually hope for future forms of sanitization to be able to detect at 
runtime when you have undefined behavior from incorrectly typed arguments 
to variadic functions.

I think the warning is the correct thing for anyone trying to write C as a 
high-level language and be type-correct and properly check before doing 
conversions that might change values unless those changes are actually 
intended, and that warning rather than lesser warnings for "C as portable 
assembler" is the appropriate default for format checking.

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-10-05 17:30                                                           ` Joseph Myers
@ 2016-10-05 22:59                                                             ` Martin Sebor
  2016-10-05 23:11                                                               ` Joseph Myers
  0 siblings, 1 reply; 115+ messages in thread
From: Martin Sebor @ 2016-10-05 22:59 UTC (permalink / raw)
  To: Joseph Myers
  Cc: Rainer Orth, David Malcolm, Gcc Patch List, Jeff Law,
	Richard Biener, Jakub Jelinek, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

> The problem is e.g. code calling getwchar and not checking for WEOF.
> 0xffffffffu does not fit in a 32-bit signed variable unless your code is
> confusing signed and unsigned freely.  And even if you consider it OK to
> check after conversion to wchar_t rather than before, the lack of a wint_t
> variable for the result seems likely to accompany a missing check.

Calling printf("%lc", x) with a wchar_t argument hardly suggests
anything about where the argument originated or what checking it
may have been subjected to.  Issuing a warning for a safe piece
of code only on the basis that there might be some other piece
of code in the program that does something wrong makes no sense.
Suggesting to the user to change the safe piece of code is
misleading and counterproductive.

> There are plenty of architectures where the ABI does require the high part
> to be extended in a particular way;

There is no high part when the wchar_t and wint_t types have
the same size, which they do not only in the case of interest but
also in all the other cases we know of.  Again, unless you know
of a counterexample this point isn't relevant to the discussion.

> I think the warning is the correct thing for anyone trying to write C as a
> high-level language and be type-correct and properly check before doing
> conversions that might change values unless those changes are actually
> intended, and that warning rather than lesser warnings for "C as portable
> assembler" is the appropriate default for format checking.

I certainly agree with this general philosophy.  Unfortunately,
the wchar_t and wint_t types on the target of interest (i386
or ILP32 on x86_64) are defined in an unhelpful way that the
-Wformat checker normally uses to diagnose real (or potential)
problems that can do do come up with their underlying types.
They just can't come up with wchar_t and wint_t.  Useful
warnings shouldn't pedantically enforce type rules in cases
where the rules serve no practical purpose or are loose enough
to allow for nonsensical implementations.  This is a basic
principle that's been followed by the vast majority of GCC
warnings and that users have come to appreciate and rely on.

The underlying problem here (and I claim a bug) is simply that
the -Wformat code doesn't distinguish the wint_t typedef (and
in C, the wchar_t typedef as well) from their underlying types
and treats the typedefs the same.  It should not, at least not
at the default warning level.  I wouldn't object to retaining
the warning with -Wpedantic but otherwise it's unhelpful.

Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-10-05 22:59                                                             ` Martin Sebor
@ 2016-10-05 23:11                                                               ` Joseph Myers
  2016-10-05 23:27                                                                 ` Martin Sebor
  0 siblings, 1 reply; 115+ messages in thread
From: Joseph Myers @ 2016-10-05 23:11 UTC (permalink / raw)
  To: Martin Sebor
  Cc: Rainer Orth, David Malcolm, Gcc Patch List, Jeff Law,
	Richard Biener, Jakub Jelinek, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

On Wed, 5 Oct 2016, Martin Sebor wrote:

> may have been subjected to.  Issuing a warning for a safe piece
> of code only on the basis that there might be some other piece
> of code in the program that does something wrong makes no sense.
> Suggesting to the user to change the safe piece of code is
> misleading and counterproductive.

It's warning because it's *bad code*.  Whether it's safe or not is 
peripheral.  Lots of warnings are for things that are dubious even if they 
may happen to do what the user wants on the platforms they care about.  
I'd consider even cases of different signedness (e.g. passing one of int 
and unsigned int to a format expecting the other) to be dubious without a 
reason why values not representable in the other type can't reach that 
code - and I'd consider cases where the types are different not just in 
signedness to be clearly worse than the cases that we only warn for with 
-Wformat-signedness.

> > There are plenty of architectures where the ABI does require the high part
> > to be extended in a particular way;
> 
> There is no high part when the wchar_t and wint_t types have
> the same size, which they do not only in the case of interest but

I mean high parts if the ABI is passing those arguments in 64-bit 
registers / stack slots and has requirements on how 32-bit arguments are 
extended for passing as 64-bit.

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-10-05 23:11                                                               ` Joseph Myers
@ 2016-10-05 23:27                                                                 ` Martin Sebor
  2016-10-05 23:55                                                                   ` Joseph Myers
  0 siblings, 1 reply; 115+ messages in thread
From: Martin Sebor @ 2016-10-05 23:27 UTC (permalink / raw)
  To: Joseph Myers
  Cc: Rainer Orth, David Malcolm, Gcc Patch List, Jeff Law,
	Richard Biener, Jakub Jelinek, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

On 10/05/2016 05:11 PM, Joseph Myers wrote:
> On Wed, 5 Oct 2016, Martin Sebor wrote:
>
>> may have been subjected to.  Issuing a warning for a safe piece
>> of code only on the basis that there might be some other piece
>> of code in the program that does something wrong makes no sense.
>> Suggesting to the user to change the safe piece of code is
>> misleading and counterproductive.
>
> It's warning because it's *bad code*.

Why?  What can go wrong and on what system?  Please name at least
one specific example.

> Whether it's safe or not is
> peripheral.  Lots of warnings are for things that are dubious even if they
> may happen to do what the user wants on the platforms they care about.
> I'd consider even cases of different signedness (e.g. passing one of int
> and unsigned int to a format expecting the other) to be dubious without a
> reason why values not representable in the other type can't reach that
> code - and I'd consider cases where the types are different not just in
> signedness to be clearly worse than the cases that we only warn for with
> -Wformat-signedness.
>
>>> There are plenty of architectures where the ABI does require the high part
>>> to be extended in a particular way;
>>
>> There is no high part when the wchar_t and wint_t types have
>> the same size, which they do not only in the case of interest but
>
> I mean high parts if the ABI is passing those arguments in 64-bit
> registers / stack slots and has requirements on how 32-bit arguments are
> extended for passing as 64-bit.

I don't understand what you mean.  We're talking about passing
an integer (wchar_t) via the ellipsis.  That's just as well
defined as passing any other integer of that size, as is (for
all practical purposes) extracting it via an integer of the
opposite signedness.  printf then stuffs the extracted wint_t
value (which is of the same size as the original wchar_t) into
a wchar_t, appends a nul, and formats the result via %ls (at
least that's how it's specified).

Please explain what can go wrong on i386 when someone calls
printf("%lc", L'a'), or on any other target GCC supports.
I will be happy to agree that the warning is justified if
you show me how this breaks and where.

Martin

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-10-05 23:27                                                                 ` Martin Sebor
@ 2016-10-05 23:55                                                                   ` Joseph Myers
  2016-10-06 21:22                                                                     ` Martin Sebor
  0 siblings, 1 reply; 115+ messages in thread
From: Joseph Myers @ 2016-10-05 23:55 UTC (permalink / raw)
  To: Martin Sebor
  Cc: Rainer Orth, David Malcolm, Gcc Patch List, Jeff Law,
	Richard Biener, Jakub Jelinek, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

On Wed, 5 Oct 2016, Martin Sebor wrote:

> On 10/05/2016 05:11 PM, Joseph Myers wrote:
> > On Wed, 5 Oct 2016, Martin Sebor wrote:
> > 
> > > may have been subjected to.  Issuing a warning for a safe piece
> > > of code only on the basis that there might be some other piece
> > > of code in the program that does something wrong makes no sense.
> > > Suggesting to the user to change the safe piece of code is
> > > misleading and counterproductive.
> > 
> > It's warning because it's *bad code*.
> 
> Why?  What can go wrong and on what system?  Please name at least
> one specific example.

It's bad because getting types wrong (more generally than just in this 
case) is a careless coding practice.  It's a code smell.  By requiring 
much more careful reasoning about whether it is OK in a particular 
context, given knowledge of ABIs and compiler optimizations, such code 
smells are liable to distract attention from other problems in the code, 
as well as being symptomatic of possible other problems in the code.

It should be possible to accommodate -Wall within a wide range of code 
styles, but you may will still need to adapt your code to it in some ways 
if you are doing things that are commonly dubious, even if you have 
reasons why they are safe in the cases that appear in your code and in the 
contexts in which that code will be used.

> > I mean high parts if the ABI is passing those arguments in 64-bit
> > registers / stack slots and has requirements on how 32-bit arguments are
> > extended for passing as 64-bit.
> 
> I don't understand what you mean.  We're talking about passing
> an integer (wchar_t) via the ellipsis.  That's just as well
> defined as passing any other integer of that size, as is (for
> all practical purposes) extracting it via an integer of the
> opposite signedness.  printf then stuffs the extracted wint_t
> value (which is of the same size as the original wchar_t) into
> a wchar_t, appends a nul, and formats the result via %ls (at
> least that's how it's specified).

Consider the powerpc64 ABI used for GNU/Linux (both BE and LE ABIs have 
this property).  A 32-bit argument is passed in a register (or in memory 
after a certain point), sign-extended to 64-bit if of a signed type and 
zero-extended to 64-bit if of an unsigned type.  wchar_t is int and wint_t 
is unsigned int in this case.  So if you pass a negative wchar_t argument, 
and the callee uses va_arg with type wint_t to access it, the compiler 
compiling the callee is entitled to optimize it on the basis that the 
value is already zero-extended to 64 bits, resulting in undefined behavior 
because the caller in fact sign-extended it.  Even if GCC doesn't optimize 
on that basis today, it could in future - and with a printf call, it's 
very likely that code compiled now will be used with a future libc.so 
shared library compiled with a newer compiler version, so printf may be 
built with a newer compiler than the code calling it.

The problem is that extracting via an integer of the opposite signedness 
is *not* defined unless the argument has a value representable in both 
types - both as a matter of ISO C rules on variadic functions, and as a 
practical matter of what various ABIs say about how sub-word arguments are 
extended when passed as function arguments.

(Negative wchar_t isn't actually a valid wide character, but without 
knowing where the wchar_t came from it's entirely possible it could have a 
value of type wchar_t that doesn't represent a valid character.  If you 
properly pass a wint_t value, then you avoid undefined behavior for 
invalid characters, since they are defined to produce an EILSEQ error.)

But my basic point is: if something can lead to analysis in this level of 
detail of whether the code is or is not safe in a particular context, 
while there is a trivial fix to the code that would short-circuit all that 
analysis, that by itself is enough evidence that the code deserves a 
warning and should be cleaned up to make it more obviously safe.

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-10-05 23:55                                                                   ` Joseph Myers
@ 2016-10-06 21:22                                                                     ` Martin Sebor
  2016-10-06 21:30                                                                       ` Joseph Myers
  0 siblings, 1 reply; 115+ messages in thread
From: Martin Sebor @ 2016-10-06 21:22 UTC (permalink / raw)
  To: Joseph Myers
  Cc: Rainer Orth, David Malcolm, Gcc Patch List, Jeff Law,
	Richard Biener, Jakub Jelinek, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

On 10/05/2016 05:54 PM, Joseph Myers wrote:
> On Wed, 5 Oct 2016, Martin Sebor wrote:
>
>> On 10/05/2016 05:11 PM, Joseph Myers wrote:
>>> On Wed, 5 Oct 2016, Martin Sebor wrote:
>>>
>>>> may have been subjected to.  Issuing a warning for a safe piece
>>>> of code only on the basis that there might be some other piece
>>>> of code in the program that does something wrong makes no sense.
>>>> Suggesting to the user to change the safe piece of code is
>>>> misleading and counterproductive.
>>>
>>> It's warning because it's *bad code*.
>>
>> Why?  What can go wrong and on what system?  Please name at least
>> one specific example.
>
> It's bad because getting types wrong (more generally than just in this
> case) is a careless coding practice.  It's a code smell.

There are lots of other "smells" that GCC doesn't warn about by
default even though it easily could, including -Wformat-signedness.

> The problem is that extracting via an integer of the opposite signedness
> is *not* defined unless the argument has a value representable in both
> types - both as a matter of ISO C rules on variadic functions, and as a
> practical matter of what various ABIs say about how sub-word arguments are
> extended when passed as function arguments.

No, sign mismatch is not the problem we're discussing.  GCC doesn't
warn for %lc when wchar_t and wint_t differ in signedness except
with -Wformat-signedness.  The cause of the warning is that wchar_t
is a distinct type from wint_t such as long from int (irrespective
of their sign), even if the two have the same size, and the checker
doesn't distinguish the typedefs from the underlying types.

My position is that when int and long have the same size, although
warning for objects of those types is appropriate because their
sizes often do differ from one target to the next, the %lc warning
is unhelpful because, unlike the underlying types, wchar_t and
wint_t are specifically designed to be interoperable and the same
propensity for a difference in size doesn't apply to them.

I did say, however, that the warning might have some merit even
in this case if there also were a target to which the code could
be ported where wchar_t and wint_t had different sizes (i.e., as
a portability warning).  But for such a portability warning to
be useful it would need to be issued by all GCC compilers, not
just when compiling for the target with that property.  (But
since neither of us knows(*) of such a target this is a moot
point.)

> But my basic point is: if something can lead to analysis in this level of
> detail of whether the code is or is not safe in a particular context,
> while there is a trivial fix to the code that would short-circuit all that
> analysis, that by itself is enough evidence that the code deserves a
> warning and should be cleaned up to make it more obviously safe.

The premise that "it's trivial to fix" is wrong and at the core
of my objection to the warning (by default; I would have no issue
with it being issued under a separate option or under -Wpedantic).

The text of the warning itself indicates that even suggesting how
to fix it isn't trivial.  GCC warns for just for the %lc directive
(not for the sign mismatch) and suggests to fix it by replacing
the %lc directive with %ld.  That's clearly wrong.  Printing
a hint with fix you're looking for (i.e., casting the argument
to wint_t) may be difficult because of the lack of location
information for local variables.

   $ cat a.c && gcc -S -Wall -m32 a.c
   typedef __WCHAR_TYPE__ wchar_t;

   void f (int x, unsigned y)
   {
     extern wchar_t w;

     __builtin_printf ("%lc", w);
     __builtin_printf ("%lc", x);
     __builtin_printf ("%lc", y);
   }
   a.c: In function ‘f’:
   a.c:7:24: warning: format ‘%lc’ expects argument of type ‘wint_t’, 
but argument 2 has type ‘wchar_t {aka long int}’ [-Wformat=]
      __builtin_printf ("%lc", w);
                         ~~^
                         %ld

But I think we're both starting to repeat ourselves without making
any headway.  I spent more time on this discussion (and the research
I did for it) than I should have an I don't view this as serious
enough of a problem to be worth spending more time on, just a minor
wart in GCC.  There are plenty others with a much better ROI.

Martin

[*] Since you've persistently dodged the question about the target
where this might be a problem I surveyed the GCC config files to
see if I could find one.  All but two of the operating systems GCC
apparently knows about define wint_t to be an alias for signed int.
The two exceptions are Ming32 and VxWorks both of which define both
wint_t and wchar_t to be unsigned short, and so those two targets
can be ruled out.

Besides these, the are a bunch of back ends that define wchar_t
to be long where, if long were wider than int, calling
printf("%lc", L'x') would be a problem.  But based on a closer
review of some of these targets it looks like they define both
int and long as 32-bit types and so there is no issue there.
I didn't look at all of them but defining wint_t to be narrower
than wchar_t would make no sense because then the former wouldn't
be able to represent all the values of the latter, which it is
specifically intended to do.  So while I can't definitively say
that no such target exists, it seems exceedingly unlikely.
Given that, it's pretty conclusive that issuing the warning is
not necessary or helpful on any target.

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-10-06 21:22                                                                     ` Martin Sebor
@ 2016-10-06 21:30                                                                       ` Joseph Myers
  0 siblings, 0 replies; 115+ messages in thread
From: Joseph Myers @ 2016-10-06 21:30 UTC (permalink / raw)
  To: Martin Sebor
  Cc: Rainer Orth, David Malcolm, Gcc Patch List, Jeff Law,
	Richard Biener, Jakub Jelinek, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

On Thu, 6 Oct 2016, Martin Sebor wrote:

> > The problem is that extracting via an integer of the opposite signedness
> > is *not* defined unless the argument has a value representable in both
> > types - both as a matter of ISO C rules on variadic functions, and as a
> > practical matter of what various ABIs say about how sub-word arguments are
> > extended when passed as function arguments.
> 
> No, sign mismatch is not the problem we're discussing.  GCC doesn't
> warn for %lc when wchar_t and wint_t differ in signedness except
> with -Wformat-signedness.  The cause of the warning is that wchar_t
> is a distinct type from wint_t such as long from int (irrespective
> of their sign), even if the two have the same size, and the checker
> doesn't distinguish the typedefs from the underlying types.

For most configurations of GCC, wint_t is unsigned int (the default from 
defaults.h).  Only a few specific (OS, CPU) pairs make a different choice.

> I did say, however, that the warning might have some merit even
> in this case if there also were a target to which the code could
> be ported where wchar_t and wint_t had different sizes (i.e., as
> a portability warning).  But for such a portability warning to
> be useful it would need to be issued by all GCC compilers, not
> just when compiling for the target with that property.  (But
> since neither of us knows(*) of such a target this is a moot
> point.)

As I pointed out, wint_t and wchar_t are ABI-incompatible when passed to 
functions on powerpc64-linux-gnu, because of the different signedness, 
even though they have the same size.

Thus, diagnosing the wrong choice of type is useful as a portability 
warning.  Unfortunately, it's hard to diagnose it on architectures where 
wchar_t and wint_t are the same type or too similar, but by diagnosing 
where they are sufficiently different at the C level (but possibly not at 
the ABI level), code can be fixed so it doesn't break in cases where the 
difference matters.

> The text of the warning itself indicates that even suggesting how
> to fix it isn't trivial.  GCC warns for just for the %lc directive
> (not for the sign mismatch) and suggests to fix it by replacing
> the %lc directive with %ld.  That's clearly wrong.  Printing

It's true the hint is wrong, but the warning is right.

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-10-04 23:30                                             ` Martin Sebor
  2016-10-05  0:22                                               ` Joseph Myers
@ 2016-10-13  9:23                                               ` Rainer Orth
  2016-10-13 16:17                                                 ` Martin Sebor
  1 sibling, 1 reply; 115+ messages in thread
From: Rainer Orth @ 2016-10-13  9:23 UTC (permalink / raw)
  To: Martin Sebor
  Cc: David Malcolm, Gcc Patch List, Joseph Myers, Jeff Law,
	Richard Biener, Jakub Jelinek, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

[-- Attachment #1: Type: text/plain, Size: 3727 bytes --]

Hi Martin,

>> as it happens, I'd already started bootstraps with your patch before
>> your mail arrived :-)
>
> Thanks for your help getting to the bottom of this!
>
>>
>> We're left with
>>
>> FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-1.c (test for excess errors)
>> FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-4.c (test for excess errors)
>>
>> for 32 bit and
>>
>> FAIL: gcc.dg/tree-ssa/builtin-sprintf-warn-4.c (test for excess errors)
>>
>> for 64 bit on both i386-pc-solaris2.12 and sparc-sun-solaris2.12.
>>
>> In the 32-bit builtin-sprintf-warn-1.c case, there are many instances of
>>
>> Excess errors:
>> /vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:224:3:
>> warning: format '%lc' expects argument of type 'wint_t', but argument 5
>> has type 'int' [-Wformat=]
>
> I've built the sparc-sun-solaris2.12 toolchain and reproduced these
> warnings.  They are vestiges of those I saw and some of which I fixed
> before.  The problem is that %lc expects a wint_t argument which on
> this target is an alias for long in but the argument of 0 has type
> int.  The warning is coming out of the -Wformat checker which doesn't
> seem to care that int and long have the same size.  I've committed
> r240758 that should fix the remaining warnings of this kind but long
> term I think GCC should change to avoid warning in this case (Clang
> doesn't).
>
>>
>> while the second is
>>
>> Excess errors:
>> /vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c:15:23:
>> warning: writing a terminating nul past the end of the destination
>> [-Wformat-length=]/vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c:30:21:
>> warning: writing format character '4' at offset 3 past the end of the
>> destination [-Wformat-length=]
>> /vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c:46:21:
>> warning: writing format character '4' at offset 3 past the end of the
>> destination [-Wformat-length=]
>> /vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c:61:25:
>> warning: writing a terminating nul past the end of the destination
>> [-Wformat-length=]
>> /vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-4.c:74:22:
>> warning: '%-s' directive writing 4 bytes into a region of size 1
>> [-Wformat-length=]
>>
>> I've no idea yet why in the first error message two different messages
>> are joined into one line.  Probably something with DejaGnu mangling the
>> output...
>
> I've reproduced this as well and it took me a while to see the
> problem.  It turns out that the target specifier I used in the
> test (*-*-*-*) happened to match my native target
> x86_64-pc-linux-gnu but not sparc-sun-solaris2.12.  Let me fix
> that in the next patch.  Hopefully with that all the remaining
> failures should clear up.
>
> Thanks again for your help and patience!

No worries: I've refreshed your patch on top of Thomas Preud'homme's for
PR testsuite/77710 and found that one more bit is needed to fix this
completely.  32-bit Solaris shows three more warnings:

/vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:1355:3: warning: format '%lc' expects argument of type 'wint_t', but argument 6 has type 'int' [-Wformat=]
/vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:1356:3: warning: format '%lc' expects argument of type 'wint_t', but argument 6 has type 'int' [-Wformat=]
/vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:1357:3: warning: format '%lc' expects argument of type 'wint_t', but argument 6 has type 'int' [-Wformat=]

Fixed as follows:


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: pr77735-sol2.patch --]
[-- Type: text/x-patch, Size: 859 bytes --]

# HG changeset patch
# Parent  1aaf616a61b8ea3ecff9313e059a1e85571cdde1
[testsuite] Fix 32-bit gcc.dg/tree-ssa/builtin-sprintf-warn-1.c on Solaris

diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c
@@ -1352,9 +1352,9 @@ void test_snprintf_chk_c_const (void)
   T (3, "%c_%c", '1', '2');      /* { dg-warning "output truncated" } */
 
   /* Wide characters.  */
-  T (0, "%lc",  0);
-  T (1, "%lc",  0);
-  T (2, "%lc",  0);
+  T (0, "%lc",  (wint_t)0);
+  T (1, "%lc",  (wint_t)0);
+  T (2, "%lc",  (wint_t)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

[-- Attachment #3: Type: text/plain, Size: 292 bytes --]


With this one and your refreshed patch, all failures are gone now for
i386-pc-solaris2.12, sparc-sun-solaris2.12, and x86_64-pc-linux-gnu.

	Rainer

-- 
-----------------------------------------------------------------------------
Rainer Orth, Center for Biotechnology, Bielefeld University

^ permalink raw reply	[flat|nested] 115+ messages in thread

* Re: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)
  2016-10-13  9:23                                               ` Rainer Orth
@ 2016-10-13 16:17                                                 ` Martin Sebor
  0 siblings, 0 replies; 115+ messages in thread
From: Martin Sebor @ 2016-10-13 16:17 UTC (permalink / raw)
  To: Rainer Orth
  Cc: David Malcolm, Gcc Patch List, Joseph Myers, Jeff Law,
	Richard Biener, Jakub Jelinek, Bernd Schmidt,
	Manuel López-Ibáñez, Florian Weimer

> No worries: I've refreshed your patch on top of Thomas Preud'homme's for
> PR testsuite/77710 and found that one more bit is needed to fix this
> completely.  32-bit Solaris shows three more warnings:
>
> /vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:1355:3: warning: format '%lc' expects argument of type 'wint_t', but argument 6 has type 'int' [-Wformat=]
> /vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:1356:3: warning: format '%lc' expects argument of type 'wint_t', but argument 6 has type 'int' [-Wformat=]
> /vol/gcc/src/hg/trunk/local/gcc/testsuite/gcc.dg/tree-ssa/builtin-sprintf-warn-1.c:1357:3: warning: format '%lc' expects argument of type 'wint_t', but argument 6 has type 'int' [-Wformat=]

Rats!  I overlooked those in followup patch I committed to fix
the others.  I had tested the change with a 32-bit cross compiler
but I still see them in the 32-bit Solaris cross compiler, though
not in the i366 one.  I assumed the i386 compiler was a good enough
proxy but now that I've checked more carefully I see that it warns
for %lc with a wchar_t argument such as L'a' but not for int such
as 0, while the 32-bit Solaris compiler for %lc with an int argument
and not for wchar_t.

In the i386 compiler wchar_t is long and wint_t is unsigned int while
in the Solaris one both wchar_t and wint_t are long int.  Even though
these types and arguments are the same width (and on Solaris even the
same sign), -Wformat still warns.

I've fixed fix this in the test in r241123.  Since I didn't manage
to convince Joseph that the warning is unhelpful in our discussion
last week I wasn't going to pursue it but I've now changed my mind.
The warning is obviously detrimental to portability so I've raised
bug 77970 for it.

Thanks
Martin

>
> Fixed as follows:
>
>
>
>
> With this one and your refreshed patch, all failures are gone now for
> i386-pc-solaris2.12, sparc-sun-solaris2.12, and x86_64-pc-linux-gnu.
>
> 	Rainer
>

^ permalink raw reply	[flat|nested] 115+ messages in thread

end of thread, other threads:[~2016-10-13 16:17 UTC | newest]

Thread overview: 115+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-07-01 18:15 [PATCH] - improve sprintf buffer overflow detection (middle-end/49905) Martin Sebor
2016-07-04 10:59 ` Richard Biener
2016-07-04 16:23   ` Martin Sebor
2016-07-04 16:44     ` Jakub Jelinek
2016-07-04 18:10       ` Bernd Schmidt
2016-07-04 18:17       ` Martin Sebor
2016-07-05 10:11     ` Richard Biener
2016-07-18 21:59       ` Martin Sebor
2016-07-21 20:33         ` Jeff Law
2016-07-21 21:57           ` Martin Sebor
2016-07-22 19:18             ` Jeff Law
2016-07-21 21:48         ` Jeff Law
2016-08-04 21:13           ` Martin Sebor
2016-08-08 19:30             ` Jeff Law
2016-08-12  2:14               ` Martin Sebor
2016-08-12 15:48                 ` Joseph Myers
2016-08-12 16:14                   ` Martin Sebor
2016-08-18 18:23                 ` Jeff Law
2016-08-19 15:30                   ` Martin Sebor
2016-08-19 15:34                     ` Jeff Law
2016-08-20  3:11                       ` Trevor Saunders
2016-08-22 12:47                         ` Florian Weimer
2016-08-23 21:57                       ` Martin Sebor
2016-08-23 23:00                         ` Joseph Myers
2016-08-24 16:41                           ` Martin Sebor
2016-08-24 18:54                             ` Florian Weimer
2016-08-24 19:19                               ` Martin Sebor
2016-08-24 22:04                             ` Joseph Myers
2016-08-24 23:08                               ` Martin Sebor
2016-08-25 23:02                                 ` Joseph Myers
2016-09-08 19:31                             ` Jeff Law
2016-09-08 19:39                               ` Martin Sebor
2016-08-23 23:42                         ` Manuel López-Ibáñez
2016-08-24 20:16                           ` Martin Sebor
2016-08-24 23:15                             ` Manuel López-Ibáñez
2016-09-08 19:21                               ` Jeff Law
2016-09-08 19:19                       ` Martin Sebor
2016-09-08 21:00                         ` David Malcolm
2016-09-08 22:11                           ` Martin Sebor
2016-09-08 22:34                         ` Joseph Myers
2016-09-12  8:07                           ` Martin Sebor
2016-09-12 22:11                             ` Joseph Myers
2016-09-12 22:22                               ` Martin Sebor
2016-09-12 23:48                                 ` Joseph Myers
2016-09-16 18:12                             ` Jeff Law
2016-09-20 20:15                               ` Martin Sebor
2016-09-20 20:33                                 ` David Malcolm
2016-09-21  6:11                                   ` Martin Sebor
2016-09-16 18:47                             ` David Malcolm
2016-09-20 18:27                               ` Martin Sebor
2016-09-21 19:46                                 ` Gerald Pfeifer
2016-09-21 20:00                                   ` Martin Sebor
2016-09-21 22:06                                     ` Jakub Jelinek
2016-09-21 22:10                                     ` Gerald Pfeifer
2016-09-22  9:11                                 ` Rainer Orth
2016-09-22 12:53                                   ` Rainer Orth
2016-09-22 15:11                                     ` Martin Sebor
2016-09-23 14:07                                       ` Rainer Orth
2016-10-03 18:05                                         ` Martin Sebor
2016-10-04  9:29                                           ` Rainer Orth
2016-10-04 23:30                                             ` Martin Sebor
2016-10-05  0:22                                               ` Joseph Myers
2016-10-05  0:31                                                 ` Martin Sebor
2016-10-05 15:40                                                   ` Joseph Myers
2016-10-05 15:45                                                     ` Jakub Jelinek
2016-10-05 16:11                                                       ` Joseph Myers
2016-10-05 16:20                                                     ` Martin Sebor
2016-10-05 16:27                                                       ` Joseph Myers
2016-10-05 17:12                                                         ` Martin Sebor
2016-10-05 17:30                                                           ` Joseph Myers
2016-10-05 22:59                                                             ` Martin Sebor
2016-10-05 23:11                                                               ` Joseph Myers
2016-10-05 23:27                                                                 ` Martin Sebor
2016-10-05 23:55                                                                   ` Joseph Myers
2016-10-06 21:22                                                                     ` Martin Sebor
2016-10-06 21:30                                                                       ` Joseph Myers
2016-10-13  9:23                                               ` Rainer Orth
2016-10-13 16:17                                                 ` Martin Sebor
2016-09-24 18:14                                       ` Andreas Schwab
2016-09-28 19:03                                         ` Martin Sebor
2016-09-22 15:04                                   ` Martin Sebor
2016-09-21  7:13                             ` Markus Trippelsdorf
2016-09-21 10:57                               ` Christophe Lyon
2016-09-21 14:38                                 ` Martin Sebor
2016-09-21 14:38                               ` Martin Sebor
2016-09-21 14:41                                 ` Alexander Monakov
2016-09-21 15:04                                   ` Szabolcs Nagy
2016-09-21 18:04                                     ` Pedro Alves
2016-09-21 15:24                                   ` Martin Sebor
2016-09-21 15:40                               ` Jeff Law
2016-09-16 17:07                         ` Jeff Law
2016-09-27  0:17                         ` [BUILDROBOT] tic6x-uclinux: undefined reference to `gnu_libc_printf_pointer_format(tree_node*, char const**)' (was: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)) Jan-Benedict Glaw
2016-09-27 17:52                           ` [BUILDROBOT] tic6x-uclinux: undefined reference to `gnu_libc_printf_pointer_format(tree_node*, char const**)' Martin Sebor
2016-09-28 12:41                           ` [BUILDROBOT] tic6x-uclinux: undefined reference to `gnu_libc_printf_pointer_format(tree_node*, char const**)' (was: [PATCH] - improve sprintf buffer overflow detection (middle-end/49905)) Segher Boessenkool
2016-09-09 18:57                     ` [PATCH] - improve sprintf buffer overflow detection (middle-end/49905) Ian Lance Taylor
2016-09-09 20:33                       ` Martin Sebor
2016-07-22 19:57         ` Jakub Jelinek
2016-07-22 22:13         ` Jeff Law
2016-08-11 23:36           ` Martin Sebor
2016-07-29 22:51         ` Joseph Myers
2016-08-11 21:56           ` Martin Sebor
2016-07-12  8:31 ` Florian Weimer
2016-07-12  9:51 ` Florian Weimer
2016-07-12  9:54   ` Jakub Jelinek
2016-07-12 10:01     ` Florian Weimer
2016-07-12 14:45       ` Martin Sebor
2016-07-12 15:01         ` David Malcolm
2016-07-12 15:59           ` Martin Sebor
2016-07-12 16:13             ` Manuel López-Ibáñez
2016-07-12 22:36               ` Martin Sebor
2016-07-13  8:18                 ` Marek Polacek
2016-07-13  9:08                 ` David Malcolm
2016-07-12 12:32 ` Bernd Schmidt
2016-07-12 21:43   ` Martin Sebor
2016-07-14  1:57 ` Manuel López-Ibáñez

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).