diff mbox

enhance buffer overflow warnings (and c/53562)

Message ID 2ed2287a-fe14-dbbd-9372-6f09f055b9bf@gmail.com
State New
Headers show

Commit Message

Martin Sebor Oct. 30, 2016, 10:45 p.m. UTC
Attached is an updated patch that adds checks for excessive sizes
and bounds (those in excess of SIZE_MAX / 2), and also enables
the same checking for strcat and strncat).  This version also
fixes an issue with the interpretation of anti-ranges in the
first patch.  The improvements exposed two bugs in the regression
tests.

I'm wondering if it would make sense to either tighten up a bit
the checks for excessive sizes and bounds or add a customization
option to make it possible to specify a lower threshold.  The
patch currently warns on sizes and bounds that exceed
SIZE_MAX / 2.  It's very unlikely that any string or memory
operation will involve anywhere near that much data, and more
likely that such large numbers indicate bugs.  I would expect
even half that to be generous in ILP32, and even less in LP64.
Reducing the threshold for the warning could help find even
more bugs and the risk of false positives is IMO negligible.
Providing an option to specify the threshold would be in line
with other such options, including the -Walloca checker.  Does
anyone have any concerns with going down this path or suggestions?

Martin

On 10/27/2016 08:19 PM, Martin Sebor wrote:
> The attached patch enhances the compile-time detection of buffer
> overflow in functions like __builtin___memcpy_chk to consider
> non-constant lengths known to be in a certain range and warn
> when the lower bound of the range doesn't fit in the destination
> object.
>
> The patch does the same thing for the non-checking functions like
> __builtin_memcpy and issues buffer overflow warnings for those.
> For string functions like __builtin_strcpy, the patch also makes
> use of ranges of lengths of non-constant strings.
>
> To make reasoning about the warnings easier (and to help with
> debugging the problems), the patch also extends the warnings to
> print the ranges of lengths and sizes of the operands.  The text
> and content of the warning messages is based on those issued by
> the -Wformat-length warning pass.
>
> Finally, as requested in bug 53562, the patch adds a new warning
> option, -Wstringop-overflow, to control these warnings (the option
> is on by default).  I chose a different name for the option than
> suggested in the bug to avoid giving the impression that it
> actually inserts the checking calls (all it does is warn on
> buffer overflows detectable at compile-time).
>
> I was originally going to submit a more modest version of this
> patch as part of a bigger project I'm working on (bug 77608) but
> then decided to submit this one first because it's independent of
> the other.
>
> Possible enhancements include letting the option accept a level
> argument and at level 2 using the upper bound of the size or
> string length ranges similarly to the -Wformat-length option.
> With that, the following could be diagnosed as a potential
> buffer overflow:
>
>   char d[5];
>   strcpy (d, x ? "123" : "123456");
>
> Thanks
> Martin

Comments

Tobias Burnus Oct. 31, 2016, 12:39 p.m. UTC | #1
Martin Sebor wrote:
> Attached is an updated patch that adds checks for excessive sizes
> and bounds (those in excess of SIZE_MAX / 2), and also enables
> the same checking for strcat and strncat).  This version also
> fixes an issue with the interpretation of anti-ranges in the
> first patch.  The improvements exposed two bugs in the regression
> tests.

If I apply this patch to my local trunk - and try to bootstrap GCC,
bootstrapping fails (on x86-64_gnu-linux) as following. I have not
tried to figure out whether the warning (-Werror) makes sense or not.

../../gcc/emit-rtl.c: In function ‘rtx_note* make_note_raw(insn_note)’:
../../gcc/emit-rtl.c:3933:59: error: void* memset(void*, int, size_t) writing 8 bytes into a region of size 0 overflows the destination [-Werror=stringop-overflow]
   memset (&NOTE_DATA (note), 0, sizeof (NOTE_DATA (note)));


where NOTE_DATA is defined in rtl.h as

/* Opaque data.  */
#define NOTE_DATA(INSN)         RTL_CHECKC1 (INSN, 3, NOTE)


Cheers,

Tobias
diff mbox

Patch

PR c/53562 - Add -Werror= support for -D_FORTIFY_SOURCE / __builtin___memcpy_chk

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

	PR c/53562
	* c.opt (-Wstringop-overflow): New option.

gcc/ChangeLog:
2016-10-30  Martin Sebor  <msebor@redhat.com>

	PR c/53562
	* builtins.c (expand_builtin_strcat, expand_builtin_strncat): New
	functions.
	(get_size_range, check_sizes, check_strncat_sizes): Same.
	(expand_builtin_memcpy): Call check sizes.
	(expand_builtin_mempcpy): Same.
	(expand_builtin_strcpy): Same.
	(expand_builtin_strncpy): Same.
	(expand_builtin_memset): Same,
	(expand_builtin_bzero): Same.
	(expand_builtin_memory_chk): Same.
	(maybe_emit_sprintf_chk_warning): Same.
	(expand_builtin): Handle strcat and strncat.
	* doc/invoke.texi (Warning Options): Document -Wstringop-overflow.

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

	PR c/53562
	* c-c++-common/Wsizeof-pointer-memaccess2.c: Adjust expected diagnostic.
	* g++.dg/ext/builtin-object-size3.C (bar): Same.
	* g++.dg/ext/strncpy-chk1.C: Same.
	* g++.dg/torture/Wsizeof-pointer-memaccess1.C: Same.
	* gcc.c-torture/compile/pr55569.c: Disable -Wstringop-overflow.
	* gcc.dg/Wobjsize-1.c: Adjust expected diagnostic.
	* gcc.dg/attr-alloc_size.c: Same.
	* gcc.dg/builtin-stringop-chk-1.c: Adjust expected diagnostic.
	* gcc.dg/builtin-stringop-chk-2.c: Same.
	* gcc.dg/builtin-stringop-chk-4.c: New test.
	* gcc.dg/builtin-strncat-chk-1.c: Adjust expected diagnostic..
	* gcc.dg/memcpy-2.c: Same.
	* gcc.dg/pr40340-1.c: Same.
	* gcc.dg/pr40340-2.c (main): Same.
	* gcc.dg/pr40340-5.c (main): Same.
	* gcc.dg/torture/Wsizeof-pointer-memaccess1.c: Same.
	* gcc.dg/torture/pr71132.c: Disable -Wstringop-overflow.

diff --git a/gcc/builtins.c b/gcc/builtins.c
index facecd3..f505ec2 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -67,7 +67,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "internal-fn.h"
 #include "case-cfn-macros.h"
 #include "gimple-fold.h"
-
+#include "intl.h"
 
 struct target_builtins default_target_builtins;
 #if SWITCHABLE_TARGET
@@ -125,9 +125,11 @@  static rtx expand_builtin_mempcpy (tree, rtx, machine_mode);
 static rtx expand_builtin_mempcpy_with_bounds (tree, rtx, machine_mode);
 static rtx expand_builtin_mempcpy_args (tree, tree, tree, rtx,
 					machine_mode, int, tree);
+static rtx expand_builtin_strcat (tree, rtx);
 static rtx expand_builtin_strcpy (tree, rtx);
 static rtx expand_builtin_strcpy_args (tree, tree, rtx);
 static rtx expand_builtin_stpcpy (tree, rtx, machine_mode);
+static rtx expand_builtin_strncat (tree, rtx);
 static rtx expand_builtin_strncpy (tree, rtx);
 static rtx builtin_memset_gen_str (void *, HOST_WIDE_INT, machine_mode);
 static rtx expand_builtin_memset (tree, rtx, machine_mode);
@@ -2967,6 +2969,211 @@  expand_builtin_memcpy_args (tree dest, tree src, tree len, rtx target, tree exp)
   return dest_addr;
 }
 
+/* Fill the 2-element RANGE array with the minimum and maximum values
+   EXP is known to have and return true, otherwise null and return
+   false.  */
+
+static bool
+get_size_range (tree exp, tree range[2])
+{
+  if (tree_fits_uhwi_p (exp))
+    {
+      range[0] = range[1] = exp;
+      return true;
+    }
+
+  if (TREE_CODE (exp) == SSA_NAME)
+    {
+      wide_int min, max;
+      enum value_range_type range_type = get_range_info (exp, &min, &max);
+
+      if (range_type == VR_RANGE)
+	{
+	  /* Interpret the bound in the variable's type.  */
+	  range[0] = wide_int_to_tree (TREE_TYPE (exp), min);
+	  range[1] = wide_int_to_tree (TREE_TYPE (exp), max);
+	  return true;
+	}
+      else if (range_type == VR_ANTI_RANGE)
+	{
+	  /* An anti-range implies the original variable is signed and
+	     its lower bound is negative and the upper bound positive.
+	     Since that means that the expression's value could be zero
+	     nothing interesting can be inferred from this.  */
+	}
+    }
+
+  range[0] = NULL_TREE;
+  range[1] = NULL_TREE;
+  return false;
+}
+
+/* Try to verify that the sizes and lengths of the arguments to a string
+   manipulation function given by EXP are within valid bounds and that
+   the operation does not lead to buffer overflow.  Arguments other than
+   EXP may be null.  When non-null, the arguments have the following
+   meaning:
+   SIZE is the user-supplied size argument to the function (such as in
+   memcpy(d, s, SIZE) or strncpy(d, s, SIZE).  It specifies the exact
+   number of bytes to write.
+   MAXLEN is the user-supplied bound on the length of the source sequence
+   (such as in strncat(d, s, N).  It specifies the upper limit on the number
+   of bytes to write.
+   SLEN is the length of the source sequence (such as in strcpy(d, s),
+   SLEN = strlen(s)).
+   OBJSIZE is the size of the destination object specified by the last
+   argument to the _chk builtins, typically resulting from the expansion
+   of __builtin_object_size (such as in __builtin___strcpy_chk(d, s,
+   OBJSIZE).
+
+   When SIZE is null LEN is checked to verify that it doesn't exceed
+   SIZE_MAX.
+
+   If the call is successfully verfified as safe from buffer overflow
+   the function returns true, otherwise false..  */
+
+static bool
+check_sizes (tree exp, tree size, tree maxlen, tree slen, tree objsize)
+{
+  /* The size of the largest object.  (This is way too permissive.)  */
+  tree maxobjsize = TYPE_MAX_VALUE (ptrdiff_type_node);
+
+  tree range[2] = { NULL_TREE, NULL_TREE };
+
+  if (!size && !maxlen)
+    {
+      /* Handle strlen but not snprintf.  */
+      size = slen ? slen : maxobjsize;
+    }
+
+  if (!objsize)
+    objsize = maxobjsize;
+
+  /* The SIZE is exact if it's non-null, constant, and in range of
+     unsigned HOST_WIDE_INT.  */
+  bool exactsize = size && tree_fits_uhwi_p (size);
+
+  if (size)
+    get_size_range (size, range);
+
+  /* First check the number of bytes to be written against the maximum
+     object size.  */
+  if (range[0] && tree_int_cst_lt (maxobjsize, range[0]))
+    {
+      location_t loc = tree_nonartificial_location (exp);
+
+      if (range[0] == range[1])
+	warning_at (loc, OPT_Wstringop_overflow,
+		    "%K%D specified size %wu "
+		    "exceeds maximum object size %wu",
+		    exp, get_callee_fndecl (exp),
+		    tree_to_uhwi (range[0]),
+		    tree_to_uhwi (maxobjsize));
+	  else
+	    warning_at (loc, OPT_Wstringop_overflow,
+			"%K%D specified size between %wu and %wu "
+			"exceeds maximum object size %wu",
+			exp, get_callee_fndecl (exp),
+			tree_to_uhwi (range[0]),
+			tree_to_uhwi (range[1]),
+			tree_to_uhwi (maxobjsize));
+      return false;
+    }
+
+  /* Next check the number of bytes to be written against the destination
+     object size.  */
+  if (range[0] || !exactsize || integer_all_onesp (size))
+    {
+      if (range[0]
+	  && ((tree_fits_uhwi_p (objsize)
+	       && tree_int_cst_lt (objsize, range[0]))
+	      || (tree_fits_uhwi_p (size)
+		  && tree_int_cst_lt (size, range[0]))))
+	{
+	  unsigned HOST_WIDE_INT uwir0 = tree_to_uhwi (range[0]);
+
+	  location_t loc = tree_nonartificial_location (exp);
+
+	  if (range[0] == range[1])
+	    warning_at (loc, OPT_Wstringop_overflow,
+			(uwir0 == 1
+			 ? G_("%K%D writing %wu byte into a region "
+			      "of size %wu overflows the destination")
+			 : G_("%K%D writing %wu bytes into a region "
+			      "of size %wu overflows the destination")),
+			exp, get_callee_fndecl (exp), uwir0,
+			tree_to_uhwi (objsize));
+	  else
+	    warning_at (loc, OPT_Wstringop_overflow,
+			"%K%D writing between %wu and %wu bytes "
+			"into a region of size %wu overflows "
+			"the destination",
+			exp, get_callee_fndecl (exp), uwir0,
+			tree_to_uhwi (range[1]), tree_to_uhwi (objsize));
+
+	  /* Return error when an overflow has been detected.  */
+	  return false;
+	}
+    }
+
+  /* Check the maximum length of the source sequence against the size
+     of the destination object if known, or against the maximum size
+     of an object.  */
+  if (maxlen)
+    {
+      get_size_range (maxlen, range);
+
+      if (range[0] && objsize && tree_fits_uhwi_p (objsize))
+	{
+	  if (objsize != maxobjsize && tree_int_cst_lt (objsize, range[0]))
+	    {
+	      location_t loc = tree_nonartificial_location (exp);
+
+	      if (range[0] == range[1])
+		warning_at (loc, OPT_Wstringop_overflow,
+			    "%K%D specified bound %wu "
+			    "exceeds the size %wu of the destination",
+			    exp, get_callee_fndecl (exp),
+			    tree_to_uhwi (range[0]),
+			    tree_to_uhwi (objsize));
+	      else
+		warning_at (loc, OPT_Wstringop_overflow,
+			    "%K%D specified bound between %wu and %wu "
+			    " exceeds the size %wu of the destination",
+			    exp, get_callee_fndecl (exp),
+			    tree_to_uhwi (range[0]),
+			    tree_to_uhwi (range[1]),
+			    tree_to_uhwi (objsize));
+	      return false;
+	    }
+	  else if (tree_int_cst_lt (maxobjsize, range[0]))
+	    {
+	      location_t loc = tree_nonartificial_location (exp);
+
+	      if (range[0] == range[1])
+		warning_at (loc, OPT_Wstringop_overflow,
+			    "%K%D specified bound %wu "
+			    "exceeds maximum object size %wu",
+			    exp, get_callee_fndecl (exp),
+			    tree_to_uhwi (range[0]),
+			    tree_to_uhwi (maxobjsize));
+	      else
+		warning_at (loc, OPT_Wstringop_overflow,
+			    "%K%D specified bound between %wu and %wu "
+			    " exceeds maximum object size %wu",
+			    exp, get_callee_fndecl (exp),
+			    tree_to_uhwi (range[0]),
+			    tree_to_uhwi (range[1]),
+			    tree_to_uhwi (maxobjsize));
+
+	      return false;
+	    }
+	}
+    }
+
+  return true;
+}
+
 /* Expand a call EXP to the memcpy builtin.
    Return NULL_RTX if we failed, the caller should emit a normal call,
    otherwise try to get the result in TARGET, if convenient (and in
@@ -2983,6 +3190,14 @@  expand_builtin_memcpy (tree exp, rtx target)
       tree dest = CALL_EXPR_ARG (exp, 0);
       tree src = CALL_EXPR_ARG (exp, 1);
       tree len = CALL_EXPR_ARG (exp, 2);
+
+      tree objsize = NULL_TREE;
+      unsigned HOST_WIDE_INT uhwisize;
+      if (compute_builtin_object_size (dest, 1, &uhwisize))
+	objsize = build_int_cst (sizetype, uhwisize);
+
+      check_sizes (exp, len, /*maxlen=*/NULL_TREE, NULL_TREE, objsize);
+
       return expand_builtin_memcpy_args (dest, src, len, target, exp);
     }
 }
@@ -3037,10 +3252,21 @@  expand_builtin_mempcpy (tree exp, rtx target, machine_mode mode)
       tree dest = CALL_EXPR_ARG (exp, 0);
       tree src = CALL_EXPR_ARG (exp, 1);
       tree len = CALL_EXPR_ARG (exp, 2);
-      return expand_builtin_mempcpy_args (dest, src, len,
-					  target, mode, /*endp=*/ 1,
-					  exp);
+
+      tree destsize = NULL_TREE;
+      unsigned HOST_WIDE_INT objsize;
+      if (compute_builtin_object_size (dest, 1, &objsize))
+	destsize = build_int_cst (sizetype, objsize);
+
+      /* Avoid expanding mempcpy into memcpy when the call is determined
+	 to overflow the buffer.  This also prevents the same overflow
+	 from being diagnosed again when expanding memcpy.  */
+      if (check_sizes (exp, len, NULL_TREE, NULL_TREE, destsize))
+	return expand_builtin_mempcpy_args (dest, src, len,
+					    target, mode, /*endp=*/ 1,
+					    exp);
     }
+  return NULL_RTX;
 }
 
 /* Expand an instrumented call EXP to the mempcpy builtin.
@@ -3212,6 +3438,50 @@  expand_movstr (tree dest, tree src, rtx target, int endp)
   return target;
 }
 
+/* Do some very basic size validation of a call to the strcpy builtin
+   given by EXP.  Return NULL_RTX to have the built-in expand to a call
+   to the library function.  */
+
+static rtx
+expand_builtin_strcat (tree exp, rtx)
+{
+  if (!validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE))
+    return NULL_RTX;
+
+  tree dest = CALL_EXPR_ARG (exp, 0);
+  tree src = CALL_EXPR_ARG (exp, 1);
+
+  /* There is no way here to determine the length of the string in
+     the destination to which the SRC string is being appended so
+     just diagnose cases when the srouce string is longer than
+     the destination object.  */
+
+  /* Try to determine the range of lengths that the source expression
+     refers to.  */
+  tree lenrange[2];
+  get_range_strlen (src, lenrange);
+
+  /* Try to verify that the destination is big enough for the shortest
+     string.  At a (future) stricter warning setting the longest length
+     should be used instead.  */
+  if (lenrange[0])
+    {
+      /* Try to determine the size of the destination object into
+	 which the source is being copied.  */
+      tree destsize = NULL_TREE;
+      unsigned HOST_WIDE_INT objsize;
+      if (compute_builtin_object_size (dest, 1, &objsize))
+	destsize = build_int_cst (sizetype, objsize);
+
+      /* Add one for the terminating nul.  */
+      tree len = fold_build2 (PLUS_EXPR, size_type_node, lenrange[0],
+			      size_one_node);
+      check_sizes (exp, NULL_TREE, NULL_TREE, len, destsize);
+    }
+
+  return NULL_RTX;
+}
+
 /* Expand expression EXP, which is a call to the strcpy builtin.  Return
    NULL_RTX if we failed the caller should emit a normal call, otherwise
    try to get the result in TARGET, if convenient (and in mode MODE if that's
@@ -3224,6 +3494,30 @@  expand_builtin_strcpy (tree exp, rtx target)
    {
      tree dest = CALL_EXPR_ARG (exp, 0);
      tree src = CALL_EXPR_ARG (exp, 1);
+
+     /* Try to determine the range of lengths that the source expression
+	refers to.  */
+     tree lenrange[2];
+     get_range_strlen (src, lenrange);
+
+     /* Try to verify that the destination is big enough for the shortest
+	string.  At a (future) stricter warning setting the longest length
+	should be used instead.  */
+     if (lenrange[0])
+       {
+	 /* Try to determine the size of the destination object into
+	    which the source is being copied.  */
+	 tree destsize = NULL_TREE;
+	 unsigned HOST_WIDE_INT objsize;
+	 if (compute_builtin_object_size (dest, 1, &objsize))
+	   destsize = build_int_cst (sizetype, objsize);
+
+	 /* Add one for the terminating nul.  */
+	 tree len = fold_build2 (PLUS_EXPR, size_type_node, lenrange[0],
+				 size_one_node);
+	 check_sizes (exp, NULL_TREE, NULL_TREE, len, destsize);
+       }
+
      return expand_builtin_strcpy_args (dest, src, target);
    }
    return NULL_RTX;
@@ -3334,6 +3628,130 @@  builtin_strncpy_read_str (void *data, HOST_WIDE_INT offset,
   return c_readstr (str + offset, mode);
 }
 
+/* Helper to check the sizes of sequences and the destination of calls
+   to __builtin_strncat and __builtin___strncat_chk.  Returns true on
+   success (no overflow or invalid sizes), false otherwise.  */
+
+static bool
+check_strncat_sizes (tree exp, tree objsize)
+{
+  tree dest = CALL_EXPR_ARG (exp, 0);
+  tree src = CALL_EXPR_ARG (exp, 1);
+  tree maxlen = CALL_EXPR_ARG (exp, 2);
+
+  /* Try to determine the range of lengths that the source expression
+     refers to.  */
+  tree lenrange[2];
+  get_range_strlen (src, lenrange);
+
+  /* Try to verify that the destination is big enough for the shortest
+     string.  */
+
+  if (!objsize)
+    {
+      /* If it hasn't been provided by __strncat_chk, try to determine
+	 the size of the destination object into which the source is
+	 being copied.  */
+      unsigned HOST_WIDE_INT destsize;
+      if (compute_builtin_object_size (dest, 1, &destsize))
+	objsize = build_int_cst (sizetype, destsize);
+    }
+
+  /* Add one for the terminating nul.  */
+  tree srclen = (lenrange[0]
+		 ? fold_build2 (PLUS_EXPR, size_type_node, lenrange[0],
+				size_one_node)
+		 : NULL_TREE);
+
+  /* Strncat copies at most MAXLEN bytes and always appends the terminating
+     nul so the specified upper bound should never be equal to (or greater
+     than) the size of the destination.  */
+  if (tree_fits_uhwi_p (maxlen) && tree_fits_uhwi_p (objsize)
+      && tree_int_cst_equal (objsize, maxlen))
+    {
+      warning_at (EXPR_LOCATION (exp), OPT_Wstringop_overflow,
+		  "specified bound %wu "
+		  "equals the size of the destination",
+		  tree_to_uhwi (maxlen));
+
+      return false;
+    }
+
+  if (!srclen || tree_int_cst_lt (maxlen, srclen))
+    srclen = maxlen;
+
+  /* The number of bytes to write is LEN but check_sizes will also
+     check SRCLEN if LEN's value isn't known.  */
+  return check_sizes (exp, /*size=*/NULL_TREE, maxlen, srclen, objsize);
+}
+
+/* Similar to expand_builtin_strcat, do some very basic size validation
+   of a call to the strcpy builtin given by EXP.  Return NULL_RTX to have
+   the built-in expand to a call to the library function.  */
+
+static rtx
+expand_builtin_strncat (tree exp, rtx)
+{
+  if (!validate_arglist (exp,
+			 POINTER_TYPE, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE))
+    return NULL_RTX;
+
+  tree dest = CALL_EXPR_ARG (exp, 0);
+  tree src = CALL_EXPR_ARG (exp, 1);
+  /* The upper bound on the number of bytes to write.  */
+  tree maxlen = CALL_EXPR_ARG (exp, 2);
+  /* The length of the source sequence.  */
+  tree slen = c_strlen (src, 1);
+
+  /* Try to determine the range of lengths that the source expression
+     refers to.  */
+  tree lenrange[2];
+  if (slen)
+    lenrange[0] = lenrange[1] = slen;
+  else
+    get_range_strlen (src, lenrange);
+
+  /* Try to verify that the destination is big enough for the shortest
+     string.  */
+
+  /* Try to determine the size of the destination object into which
+     the source is being copied.  */
+  tree destsize = NULL_TREE;
+  unsigned HOST_WIDE_INT objsize;
+  if (compute_builtin_object_size (dest, 1, &objsize))
+    destsize = build_int_cst (sizetype, objsize);
+
+  /* Add one for the terminating nul.  */
+  tree srclen = (lenrange[0]
+		 ? fold_build2 (PLUS_EXPR, size_type_node, lenrange[0],
+				size_one_node)
+		 : NULL_TREE);
+
+  /* Strncat copies at most MAXLEN bytes and always appends the terminating
+     nul so the specified upper bound should never be equal to (or greater
+     than) the size of the destination.  */
+  if (tree_fits_uhwi_p (maxlen) && tree_fits_uhwi_p (destsize)
+      && tree_int_cst_equal (destsize, maxlen))
+    {
+      warning_at (EXPR_LOCATION (exp), OPT_Wstringop_overflow,
+		  "specified bound %wu "
+		  "equals the size of the destination",
+		  tree_to_uhwi (maxlen));
+
+      return NULL_RTX;
+    }
+
+
+  if (!srclen || tree_int_cst_lt (maxlen, srclen))
+    srclen = maxlen;
+
+  /* The number of bytes to write is LEN but check_sizes will also
+     check SRCLEN if LEN's value isn't known.  */
+  check_sizes (exp, /*size=*/NULL_TREE, maxlen, srclen, destsize);
+
+  return NULL_RTX;
+}
+
 /* Expand expression EXP, which is a call to the strncpy builtin.  Return
    NULL_RTX if we failed the caller should emit a normal call.  */
 
@@ -3347,9 +3765,39 @@  expand_builtin_strncpy (tree exp, rtx target)
     {
       tree dest = CALL_EXPR_ARG (exp, 0);
       tree src = CALL_EXPR_ARG (exp, 1);
+      /* The number of bytes to write (not the maximum).  */
       tree len = CALL_EXPR_ARG (exp, 2);
+      /* The length of the source sequence.  */
       tree slen = c_strlen (src, 1);
 
+      /* Try to determine the range of lengths that the source expression
+	 refers to.  */
+      tree lenrange[2];
+      if (slen)
+	lenrange[0] = lenrange[1] = slen;
+      else
+	get_range_strlen (src, lenrange);
+
+      /* Try to verify that the destination is big enough for the shortest
+	 string.  At a (future) stricter warning setting the longest length
+	 should be used instead.  */
+
+      /* Try to determine the size of the destination object into which
+	 the source is being copied.  */
+      tree destsize = NULL_TREE;
+      unsigned HOST_WIDE_INT objsize;
+      if (compute_builtin_object_size (dest, 1, &objsize))
+	destsize = build_int_cst (sizetype, objsize);
+
+      /* Add one for the terminating nul.  */
+      tree srclen = (lenrange[0]
+		     ? fold_build2 (PLUS_EXPR, size_type_node, lenrange[0],
+				    size_one_node)
+		     : NULL_TREE);
+      /* The number of bytes to write is LEN but check_sizes will also
+	 check SRCLEN if LEN's value isn't known.  */
+      check_sizes (exp, len, /*maxlen=*/NULL_TREE, srclen, destsize);
+
       /* We must be passed a constant len and src parameter.  */
       if (!tree_fits_uhwi_p (len) || !slen || !tree_fits_uhwi_p (slen))
 	return NULL_RTX;
@@ -3442,6 +3890,15 @@  expand_builtin_memset (tree exp, rtx target, machine_mode mode)
       tree dest = CALL_EXPR_ARG (exp, 0);
       tree val = CALL_EXPR_ARG (exp, 1);
       tree len = CALL_EXPR_ARG (exp, 2);
+
+      /* Try to determine the size of the destination object.  */
+      tree objsize = NULL_TREE;
+      unsigned HOST_WIDE_INT uhwisize;
+      if (compute_builtin_object_size (dest, 1, &uhwisize))
+	objsize = build_int_cst (sizetype, uhwisize);
+
+      check_sizes (exp, len, NULL_TREE, NULL_TREE, objsize);
+
       return expand_builtin_memset_args (dest, val, len, target, mode, exp);
     }
 }
@@ -3633,6 +4090,14 @@  expand_builtin_bzero (tree exp)
   dest = CALL_EXPR_ARG (exp, 0);
   size = CALL_EXPR_ARG (exp, 1);
 
+  /* Try to determine the size of the destination object.  */
+  tree objsize = NULL_TREE;
+  unsigned HOST_WIDE_INT uhwisize;
+  if (compute_builtin_object_size (dest, 1, &uhwisize))
+    objsize = build_int_cst (sizetype, uhwisize);
+
+  check_sizes (exp, size, NULL_TREE, NULL_TREE, objsize);
+
   /* New argument list transforming bzero(ptr x, int y) to
      memset(ptr x, int 0, size_t y).   This is done this way
      so that if it isn't expanded inline, we fallback to
@@ -6119,12 +6584,24 @@  expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode,
 	return target;
       break;
 
+    case BUILT_IN_STRCAT:
+      target = expand_builtin_strcat (exp, target);
+      if (target)
+	return target;
+      break;
+
     case BUILT_IN_STRCPY:
       target = expand_builtin_strcpy (exp, target);
       if (target)
 	return target;
       break;
 
+    case BUILT_IN_STRNCAT:
+      target = expand_builtin_strncat (exp, target);
+      if (target)
+	return target;
+      break;
+
     case BUILT_IN_STRNCPY:
       target = expand_builtin_strncpy (exp, target);
       if (target)
@@ -9185,6 +9662,14 @@  expand_builtin_object_size (tree exp)
 
   object_size_type = tree_to_shwi (ost);
 
+  unsigned HOST_WIDE_INT bytes;
+  compute_builtin_object_size (CALL_EXPR_ARG (exp, 0), object_size_type, &bytes);
+  if (wi::fits_to_tree_p (bytes, size_type_node))
+    {
+      tree node = build_int_cstu (size_type_node, bytes);
+      return expand_normal (node);
+    }
+
   return object_size_type < 2 ? constm1_rtx : const0_rtx;
 }
 
@@ -9212,22 +9697,20 @@  expand_builtin_memory_chk (tree exp, rtx target, machine_mode mode,
   len = CALL_EXPR_ARG (exp, 2);
   size = CALL_EXPR_ARG (exp, 3);
 
-  if (! tree_fits_uhwi_p (size))
+  bool sizes_ok = check_sizes (exp, len, NULL_TREE, NULL_TREE, size);
+
+  if (!tree_fits_uhwi_p (size))
     return NULL_RTX;
 
   if (tree_fits_uhwi_p (len) || integer_all_onesp (size))
     {
-      tree fn;
-
-      if (! integer_all_onesp (size) && tree_int_cst_lt (size, len))
-	{
-	  warning_at (tree_nonartificial_location (exp),
-		      0, "%Kcall to %D will always overflow destination buffer",
-		      exp, get_callee_fndecl (exp));
-	  return NULL_RTX;
-	}
+      /* Avoid transforming the checking call to an ordinary one when
+	 an overflow has been detected or when the call couldn't be
+	 validated because the size is not constant.  */
+      if (!sizes_ok && !integer_all_onesp (size) && tree_int_cst_lt (size, len))
+	return NULL_RTX;
 
-      fn = NULL_TREE;
+      tree fn = NULL_TREE;
       /* If __builtin_mem{cpy,pcpy,move,set}_chk is used, assume
 	 mem{cpy,pcpy,move,set} is available.  */
       switch (fcode)
@@ -9313,68 +9796,84 @@  expand_builtin_memory_chk (tree exp, rtx target, machine_mode mode,
 static void
 maybe_emit_chk_warning (tree exp, enum built_in_function fcode)
 {
-  int is_strlen = 0;
-  tree len, size;
-  location_t loc = tree_nonartificial_location (exp);
+  /* The length of the source sequence of the memory operation, and
+     the size of the destination object.  */
+  tree srclen = NULL_TREE;
+  tree objsize = NULL_TREE;
+  /* The length of the sequence that the source sequence is being
+     concatenated with (as with __strcat_chk) or null if it isn't.  */
+  tree catlen = NULL_TREE;
+  /* The maximum length of the source sequence in a bounded operation
+     (such as __strncat_chk) or null if the operation isn't bounded
+     (such as __strcat_chk).  */
+  tree maxlen = NULL_TREE;
 
   switch (fcode)
     {
     case BUILT_IN_STRCPY_CHK:
     case BUILT_IN_STPCPY_CHK:
-    /* For __strcat_chk the warning will be emitted only if overflowing
-       by at least strlen (dest) + 1 bytes.  */
+      srclen = CALL_EXPR_ARG (exp, 1);
+      objsize = CALL_EXPR_ARG (exp, 2);
+      break;
+
     case BUILT_IN_STRCAT_CHK:
-      len = CALL_EXPR_ARG (exp, 1);
-      size = CALL_EXPR_ARG (exp, 2);
-      is_strlen = 1;
+      /* For __strcat_chk the warning will be emitted only if overflowing
+	 by at least strlen (dest) + 1 bytes.  */
+      catlen = CALL_EXPR_ARG (exp, 0);
+      srclen = CALL_EXPR_ARG (exp, 1);
+      objsize = CALL_EXPR_ARG (exp, 2);
       break;
+
     case BUILT_IN_STRNCAT_CHK:
+      catlen = CALL_EXPR_ARG (exp, 0);
+      srclen = CALL_EXPR_ARG (exp, 1);
+      maxlen = CALL_EXPR_ARG (exp, 2);
+      objsize = CALL_EXPR_ARG (exp, 3);
+      break;
+
     case BUILT_IN_STRNCPY_CHK:
     case BUILT_IN_STPNCPY_CHK:
-      len = CALL_EXPR_ARG (exp, 2);
-      size = CALL_EXPR_ARG (exp, 3);
+      srclen = CALL_EXPR_ARG (exp, 1);
+      maxlen = CALL_EXPR_ARG (exp, 2);
+      objsize = CALL_EXPR_ARG (exp, 3);
       break;
+
     case BUILT_IN_SNPRINTF_CHK:
     case BUILT_IN_VSNPRINTF_CHK:
-      len = CALL_EXPR_ARG (exp, 1);
-      size = CALL_EXPR_ARG (exp, 3);
+      maxlen = CALL_EXPR_ARG (exp, 1);
+      objsize = CALL_EXPR_ARG (exp, 3);
       break;
     default:
       gcc_unreachable ();
     }
 
-  if (!len || !size)
-    return;
-
-  if (! tree_fits_uhwi_p (size) || integer_all_onesp (size))
-    return;
+  /* Compute the length of shortest string the source pointer points to.  */
+  if (srclen)
+    {
+      tree lenrange[2];
+      get_range_strlen (srclen, lenrange);
+      srclen = lenrange[0];
+    }
 
-  if (is_strlen)
+  if (catlen && maxlen)
     {
-      len = c_strlen (len, 1);
-      if (! len || ! tree_fits_uhwi_p (len) || tree_int_cst_lt (len, size))
+      /* Check __strncat_chk.  There is no way to determine the length
+	 of the string to which the source string is being appended so
+	 just warn when the length of the source string is not known.  */
+      if (!check_strncat_sizes (exp, objsize))
 	return;
     }
-  else if (fcode == BUILT_IN_STRNCAT_CHK)
+
+  if (srclen)
     {
-      tree src = CALL_EXPR_ARG (exp, 1);
-      if (! src || ! tree_fits_uhwi_p (len) || tree_int_cst_lt (len, size))
-	return;
-      src = c_strlen (src, 1);
-      if (! src || ! tree_fits_uhwi_p (src))
-	{
-	  warning_at (loc, 0, "%Kcall to %D might overflow destination buffer",
-		      exp, get_callee_fndecl (exp));
-	  return;
-	}
-      else if (tree_int_cst_lt (src, size))
-	return;
+      srclen = fold_build2 (PLUS_EXPR, TREE_TYPE (srclen), srclen,
+			    size_one_node);
+      if (maxlen && tree_fits_uhwi_p (maxlen)
+	  && tree_int_cst_lt (maxlen, srclen))
+	srclen = maxlen;
     }
-  else if (! tree_fits_uhwi_p (len) || ! tree_int_cst_lt (size, len))
-    return;
 
-  warning_at (loc, 0, "%Kcall to %D will always overflow destination buffer",
-	      exp, get_callee_fndecl (exp));
+  check_sizes (exp, NULL_TREE, maxlen, srclen, objsize);
 }
 
 /* Emit warning if a buffer overflow is detected at compile time
@@ -9428,10 +9927,9 @@  maybe_emit_sprintf_chk_warning (tree exp, enum built_in_function fcode)
   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));
+  /* Add one for the terminating nul.  */
+  len = fold_build2 (PLUS_EXPR, TREE_TYPE (len), len, size_one_node);
+  check_sizes (exp, NULL_TREE, NULL_TREE, len, size);
 }
 
 /* Emit warning if a free is called with address of a variable.  */
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index e146781..6635464 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -647,6 +647,10 @@  Wsizeof-array-argument
 C ObjC C++ ObjC++ Var(warn_sizeof_array_argument) Warning Init(1)
 Warn when sizeof is applied on a parameter declared as an array.
 
+Wstringop-overflow
+C ObjC C++ ObjC++ Var(warn_stringop_overflow) Init(1) Warning
+Warn about buffer overflow in string manipulation functions.
+
 Wsuggest-attribute=format
 C ObjC C++ ObjC++ Var(warn_suggest_attribute_format) Warning
 Warn about functions which might be candidates for format attributes.
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index d9667e7..81cbe63 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -299,6 +299,7 @@  Objective-C and Objective-C++ Dialects}.
 -Wsizeof-pointer-memaccess  -Wsizeof-array-argument @gol
 -Wstack-protector -Wstack-usage=@var{len} -Wstrict-aliasing @gol
 -Wstrict-aliasing=n -Wstrict-overflow -Wstrict-overflow=@var{n} @gol
+-Wstringop-overflow @gol
 -Wsuggest-attribute=@r{[}pure@r{|}const@r{|}noreturn@r{|}format@r{]} @gol
 -Wsuggest-final-types @gol -Wsuggest-final-methods -Wsuggest-override @gol
 -Wmissing-format-attribute -Wsubobject-linkage @gol
@@ -4831,6 +4832,27 @@  comparisons, so this warning level gives a very large number of
 false positives.
 @end table
 
+@item -Wstringop-overflow
+@opindex Wstringop-overflow
+@opindex Wno-stringop-overflow
+Warn for calls to string manipulation functions such as memcpy and strcpy
+hat are determined to overflow the destination buffer.  The option works
+best with optimization enabled but it can detect a small subset of buffer
+overflows even without optimization.  In any case, the option warns about
+just a subset of buffer overflows detected by the corresponding overflow
+checking built-ins.  @xref{Object Size Checking}.  For example,
+@option{-Wstringop-overflow} will warn on the following:
+
+@smallexample
+char buf[6];
+void f (int i)
+@{
+  strcpy (buf, i < 0 ? "yellow" : "orange");
+@};
+@end smallexample
+
+The @option{-Wstringop-overflow} option is enabled by default.
+
 @item -Wsuggest-attribute=@r{[}pure@r{|}const@r{|}noreturn@r{|}format@r{]}
 @opindex Wsuggest-attribute=
 @opindex Wno-suggest-attribute=
diff --git a/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess2.c b/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess2.c
index d9ec7e2..9a02373 100644
--- a/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess2.c
+++ b/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess2.c
@@ -481,4 +481,4 @@  f4 (char *x, char **y, int z, char w[64])
   stpncpy (x, s3, sizeof (s3));
 }
 
-/* { dg-prune-output "\[\n\r\]*will always overflow\[\n\r\]*" } */
+/* { dg-prune-output "\[\n\r\]*writing\[\n\r\]*" } */
diff --git a/gcc/testsuite/g++.dg/ext/builtin-object-size3.C b/gcc/testsuite/g++.dg/ext/builtin-object-size3.C
index 09263e5..0207f9a 100644
--- a/gcc/testsuite/g++.dg/ext/builtin-object-size3.C
+++ b/gcc/testsuite/g++.dg/ext/builtin-object-size3.C
@@ -20,7 +20,7 @@  bar ()
 {
   int *p = new int;
   int *q = new int[4];
-  MEMCPY (p, "abcdefghijklmnopqrstuvwxyz", sizeof (int) + 1);		// { dg-warning "will always overflow destination buffer" }
-  MEMCPY (q, "abcdefghijklmnopqrstuvwxyz", 4 * sizeof (int) + 1);	// { dg-warning "will always overflow destination buffer" }
+  MEMCPY (p, "abcdefghijklmnopqrstuvwxyz", sizeof (int) + 1);		// { dg-warning "writing" }
+  MEMCPY (q, "abcdefghijklmnopqrstuvwxyz", 4 * sizeof (int) + 1);	// { dg-warning "writing" }
   baz (p, q);
 }
diff --git a/gcc/testsuite/g++.dg/ext/strncpy-chk1.C b/gcc/testsuite/g++.dg/ext/strncpy-chk1.C
index ebafc99..85b3977 100644
--- a/gcc/testsuite/g++.dg/ext/strncpy-chk1.C
+++ b/gcc/testsuite/g++.dg/ext/strncpy-chk1.C
@@ -9,7 +9,7 @@  struct B { char z[50]; };
 inline void
 foo (char *dest, const char *__restrict src, __SIZE_TYPE__ n)
 {
-  __builtin___strncpy_chk (dest, src, n, __builtin_object_size (dest, 0));	// { dg-warning "will always overflow" }
+  __builtin___strncpy_chk (dest, src, n, __builtin_object_size (dest, 0));	// { dg-warning "overflows" }
 }
 
 void bar (const char *, int);
diff --git a/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess1.C b/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess1.C
index 8b5c33e..2e6189b 100644
--- a/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess1.C
+++ b/gcc/testsuite/g++.dg/torture/Wsizeof-pointer-memaccess1.C
@@ -713,4 +713,4 @@  f4 (char *x, char **y, int z, char w[64])
   return z;
 }
 
-// { dg-prune-output "\[\n\r\]*will always overflow\[\n\r\]*" }
+// { dg-prune-output "\[\n\r\]*overflows\[\n\r\]*" }
diff --git a/gcc/testsuite/gcc.c-torture/compile/pr55569.c b/gcc/testsuite/gcc.c-torture/compile/pr55569.c
index cffbcfc..7708f21 100644
--- a/gcc/testsuite/gcc.c-torture/compile/pr55569.c
+++ b/gcc/testsuite/gcc.c-torture/compile/pr55569.c
@@ -1,4 +1,4 @@ 
-/* { dg-options "-ftree-vectorize" } */
+/* { dg-options "-Wno-stringop-overflow -ftree-vectorize" } */
 int *bar (void);
 
 void
@@ -6,6 +6,10 @@  foo (void)
 {
   long x;
   int *y = bar ();
-    for (x = -1 / sizeof (int); x; --x, ++y)
-       *y = 0;
+
+  /* The loop below may be optimized to a call to memset with a size
+     that's in excess of the maximum object size.  This is diagnosed
+     by the -Wstringop-overflow option. */
+  for (x = -1 / sizeof (int); x; --x, ++y)
+    *y = 0;
 }
diff --git a/gcc/testsuite/gcc.dg/Wobjsize-1.c b/gcc/testsuite/gcc.dg/Wobjsize-1.c
index 291cfb9..211e068 100644
--- a/gcc/testsuite/gcc.dg/Wobjsize-1.c
+++ b/gcc/testsuite/gcc.dg/Wobjsize-1.c
@@ -10,6 +10,6 @@  int main(int argc, char **argv)
   return 0;
 }
 
-/* { dg-warning "will always overflow destination buffer" "" { target *-*-* } 6 } */
+/* { dg-warning "writing" "" { target *-*-* } 6 } */
 /* { dg-message "file included" "included" { target *-*-* } 0 } */
 /* { dg-message "inlined from" "inlined" { target *-*-* } 0 } */
diff --git a/gcc/testsuite/gcc.dg/attr-alloc_size.c b/gcc/testsuite/gcc.dg/attr-alloc_size.c
index e8129ce..f50ba7c 100644
--- a/gcc/testsuite/gcc.dg/attr-alloc_size.c
+++ b/gcc/testsuite/gcc.dg/attr-alloc_size.c
@@ -22,15 +22,15 @@  test (void)
   strcpy (p, "Hello");
   p = malloc1 (6);
   strcpy (p, "Hello");
-  strcpy (p, "Hello World"); /* { dg-warning "will always overflow" "strcpy" } */
+  strcpy (p, "Hello World"); /* { dg-warning "writing" "strcpy" } */
   p = malloc2 (__INT_MAX__ >= 1700000 ? 424242 : __INT_MAX__ / 4, 6);
   strcpy (p, "World");
-  strcpy (p, "Hello World"); /* { dg-warning "will always overflow" "strcpy" } */
+  strcpy (p, "Hello World"); /* { dg-warning "writing" "strcpy" } */
   p = calloc1 (2, 5);
   strcpy (p, "World");
-  strcpy (p, "Hello World"); /* { dg-warning "will always overflow" "strcpy" } */
+  strcpy (p, "Hello World"); /* { dg-warning "writing" "strcpy" } */
   p = calloc2 (2, __INT_MAX__ >= 1700000 ? 424242 : __INT_MAX__ / 4, 5);
   strcpy (p, "World");
-  strcpy (p, "Hello World"); /* { dg-warning "will always overflow" "strcpy" } */
+  strcpy (p, "Hello World"); /* { dg-warning "writing" "strcpy" } */
 }
 
diff --git a/gcc/testsuite/gcc.dg/builtin-stringop-chk-1.c b/gcc/testsuite/gcc.dg/builtin-stringop-chk-1.c
index e491ff5..7689287 100644
--- a/gcc/testsuite/gcc.dg/builtin-stringop-chk-1.c
+++ b/gcc/testsuite/gcc.dg/builtin-stringop-chk-1.c
@@ -8,7 +8,10 @@ 
 extern void abort (void);
 
 #include "../gcc.c-torture/execute/builtins/chk.h"
-#include <stdarg.h>
+
+#define va_list    __builtin_va_list
+#define va_start   __builtin_va_start
+#define va_end     __builtin_va_end
 
 volatile void *vx;
 char buf1[20];
@@ -22,60 +25,61 @@  test (int arg, ...)
   char *p = &buf1[10], *q;
 
   memcpy (&buf2[19], "ab", 1);
-  memcpy (&buf2[19], "ab", 2); /* { dg-warning "will always overflow" "memcpy" } */
+  memcpy (&buf2[19], "ab", 2); /* { dg-warning "writing 2 bytes into a region of size 1" "memcpy" } */
   vx = mempcpy (&buf2[19], "ab", 1);
-  vx = mempcpy (&buf2[19], "ab", 2); /* { dg-warning "will always overflow" "mempcpy" } */
+  vx = mempcpy (&buf2[19], "ab", 2); /* { dg-warning "writing 2 " "mempcpy" } */
   memmove (&buf2[18], &buf1[10], 2);
-  memmove (&buf2[18], &buf1[10], 3); /* { dg-warning "will always overflow" "memmove" } */
+  memmove (&buf2[18], &buf1[10], 3); /* { dg-warning "writing 3 " "memmove" } */
   memset (&buf2[16], 'a', 4);
-  memset (&buf2[15], 'b', 6); /* { dg-warning "will always overflow" "memset" } */
+  memset (&buf2[15], 'b', 6); /* { dg-warning "writing 6 " "memset" } */
   strcpy (&buf2[18], "a");
-  strcpy (&buf2[18], "ab"); /* { dg-warning "will always overflow" "strcpy" } */
+  strcpy (&buf2[18], "ab"); /* { dg-warning "writing 3 " "strcpy" } */
   vx = stpcpy (&buf2[18], "a");
-  vx = stpcpy (&buf2[18], "ab"); /* { dg-warning "will always overflow" "stpcpy" } */
+  vx = stpcpy (&buf2[18], "ab"); /* { dg-warning "writing 3" "stpcpy" } */
   strncpy (&buf2[18], "a", 2);
-  strncpy (&buf2[18], "a", 3); /* { dg-warning "will always overflow" "strncpy" } */
+  strncpy (&buf2[18], "a", 3); /* { dg-warning "specified bound 3 exceeds the size 2 of the destination" "strncpy" } */
   strncpy (&buf2[18], "abc", 2);
-  strncpy (&buf2[18], "abc", 3); /* { dg-warning "will always overflow" "strncpy" } */
+  strncpy (&buf2[18], "abc", 3); /* { dg-warning "specified bound 3 exceeds the size 2 of the destination" "strncpy" } */
   memset (buf2, '\0', sizeof (buf2));
   strcat (&buf2[18], "a");
   memset (buf2, '\0', sizeof (buf2));
-  strcat (&buf2[18], "ab"); /* { dg-warning "will always overflow" "strcat" } */
+  strcat (&buf2[18], "ab"); /* { dg-warning "writing 3 " "strcat" } */
   sprintf (&buf2[18], "%s", buf1);
   sprintf (&buf2[18], "%s", "a");
-  sprintf (&buf2[18], "%s", "ab"); /* { dg-warning "will always overflow" "sprintf" } */
+  sprintf (&buf2[18], "%s", "ab"); /* { dg-warning "writing 3 " "sprintf" } */
   sprintf (&buf2[18], "a");
-  sprintf (&buf2[18], "ab"); /* { dg-warning "will always overflow" "sprintf" } */
+  sprintf (&buf2[18], "ab"); /* { dg-warning "writing 3 " "sprintf" } */
   snprintf (&buf2[18], 2, "%d", x);
   /* N argument to snprintf is the size of the buffer.
      Although this particular call wouldn't overflow buf2,
      incorrect buffer size was passed to it and therefore
      we want a warning and runtime failure.  */
-  snprintf (&buf2[18], 3, "%d", x); /* { dg-warning "will always overflow" "snprintf" } */
+  snprintf (&buf2[18], 3, "%d", x); /* { dg-warning "specified bound 3 exceeds the size 2 of the destination" "snprintf" } */
   va_start (ap, arg);
   vsprintf (&buf2[18], "a", ap);
   va_end (ap);
+
   va_start (ap, arg);
-  vsprintf (&buf2[18], "ab", ap); /* { dg-warning "will always overflow" "vsprintf" } */
+  vsprintf (&buf2[18], "ab", ap); /* { dg-warning "writing 3" "vsprintf" } */
   va_end (ap);
   va_start (ap, arg);
   vsnprintf (&buf2[18], 2, "%s", ap);
   va_end (ap);
   va_start (ap, arg);
   /* See snprintf above.  */
-  vsnprintf (&buf2[18], 3, "%s", ap); /* { dg-warning "will always overflow" "vsnprintf" } */
+  vsnprintf (&buf2[18], 3, "%s", ap); /* { dg-warning "specified bound 3 exceeds the size 2 of the destination" "vsnprintf" } */
   va_end (ap);
 
   p = p + 10;
   memset (p, 'd', 0);
-  q = strcpy (p, ""); /* { dg-warning "will always overflow" "strcpy" } */
+  q = strcpy (p, ""); /* { dg-warning "writing 1 " "strcpy" } */
 
   /* This invokes undefined behavior, since we are past the end of buf1.  */
   p = p + 10;
-  memset (p, 'd', 1); /* { dg-warning "will always overflow" "memset" } */
+  memset (p, 'd', 1); /* { dg-warning "writing 1 " "memset" } */
 
   memset (q, 'd', 0);
-  memset (q, 'd', 1); /* { dg-warning "will always overflow" "memset" } */
+  memset (q, 'd', 1); /* { dg-warning "writing 1 " "memset" } */
   q = q - 10;
   memset (q, 'd', 10);
 }
@@ -90,26 +94,26 @@  void
 test2 (const H h)
 {
   char c;
-  strncpy (&c, str, 3); /* { dg-warning "will always overflow" "strncpy" } */
+  strncpy (&c, str, 3); /* { dg-warning "specified bound 3 exceeds the size 1 of the destination" "strncpy" } */
 
   struct { char b[4]; } x;
-  sprintf (x.b, "%s", "ABCD"); /* { dg-warning "will always overflow" "sprintf" } */
+  sprintf (x.b, "%s", "ABCD"); /* { dg-warning "writing 5" "sprintf" } */
 
   unsigned int i;
-  memcpy (&i, &h, sizeof (h)); /* { dg-warning "will always overflow" "memcpy" } */
+  memcpy (&i, &h, sizeof (h)); /* { dg-warning "writing 16 " "memcpy" } */
 
   unsigned char buf[21];
-  memset (buf + 16, 0, 8); /* { dg-warning "will always overflow" "memset" } */
+  memset (buf + 16, 0, 8); /* { dg-warning "writing 8 " "memset" } */
 
   typedef struct { int i, j, k, l; } S;
   S *s[3];
-  memset (s, 0, sizeof (S) * 3); /* { dg-warning "will always overflow" "memset" } */
+  memset (s, 0, sizeof (S) * 3); /* { dg-warning "writing 48 " "memset" } */
 
   struct T { char a[8]; char b[4]; char c[10]; } t;
-  stpcpy (t.c,"Testing..."); /* { dg-warning "will always overflow" "stpcpy" } */
+  stpcpy (t.c,"Testing..."); /* { dg-warning "writing" "stpcpy" } */
 
   char b1[7];
   char b2[4];
   memset (b1, 0, sizeof (b1));
-  memset (b2, 0, sizeof (b1)); /* { dg-warning "will always overflow" "memset" } */
+  memset (b2, 0, sizeof (b1)); /* { dg-warning "writing 7" "memset" } */
 }
diff --git a/gcc/testsuite/gcc.dg/builtin-stringop-chk-2.c b/gcc/testsuite/gcc.dg/builtin-stringop-chk-2.c
index 7c2bb60..d537fb0 100644
--- a/gcc/testsuite/gcc.dg/builtin-stringop-chk-2.c
+++ b/gcc/testsuite/gcc.dg/builtin-stringop-chk-2.c
@@ -6,7 +6,7 @@ 
 /* { dg-options "-O2 -ftrack-macro-expansion=0" } */
 
 #include "../gcc.c-torture/execute/builtins/chk.h"
-   
+
 void *bar (int);
 extern void *malloc (__SIZE_TYPE__);
 
@@ -115,7 +115,7 @@  baz (const struct A *x, const unsigned char *z)
 	  else
 	    do
 	      {
-		memcpy (e, d, 513); /* { dg-warning "will always overflow" "memcpy" } */
+		memcpy (e, d, 513); /* { dg-warning "writing" "memcpy" } */
 		e += 4;
 	      }
 	    while (--h);
diff --git a/gcc/testsuite/gcc.dg/builtin-stringop-chk-3.c b/gcc/testsuite/gcc.dg/builtin-stringop-chk-3.c
new file mode 100644
index 0000000..3b0ba0f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/builtin-stringop-chk-3.c
@@ -0,0 +1,344 @@ 
+/* Test exercising buffer overflow warnings emitted for __*_chk builtins
+   in cases where the destination involves a non-constant offset into
+   an object of known size.  */
+/* { dg-do compile } */
+/* { dg-options "-O2 -ftrack-macro-expansion=0" } */
+
+#define INT_MAX      __INT_MAX__
+#define PTRDIFF_MAX  __PTRDIFF_MAX__
+#define SIZE_MAX     __SIZE_MAX__
+
+typedef __PTRDIFF_TYPE__ ptrdiff_t;
+typedef __SIZE_TYPE__    size_t;
+
+void sink (void*);
+
+/* Define memcpy as a macro (as opposed to an inline function) so that
+   warnings point to its invocation in the tests (as opposed to its
+   definition), making sure its first argument is evaluated exactly
+   once.  */
+#define memcpy(d, s, n)							\
+  do {									\
+    __typeof__ (d) __d = (d);						\
+    __builtin___memcpy_chk (__d, s, n, __builtin_object_size (__d, 1));	\
+    sink (__d);								\
+  } while (0)
+
+/* Function to generate a unique offset each time it's called.  Declared
+   (but not defined) and used to prevent GCC from making assumptions about
+   their values based on the variables uses in the tested expressions.  */
+ptrdiff_t random_value (void);
+
+/* For brevity. */
+#define X() random_value ()
+
+/* Test memcpy with a variable offset not known to be in any range
+   and with a constant number of bytes.  */
+
+void test_var_const (const void *p)
+{
+  char buf[5];
+
+  memcpy (buf + X(), p, 6);  /* { dg-warning "writing 6 bytes into a region of size 5" } */
+
+  memcpy (&buf[X()], p, 6);  /* { dg-warning "writing" } */
+
+  /* Since X() below can be assumed to be non-negative (otherwise it would
+     result in forming a pointer before the beginning of BUF), then because
+     of the +1 added to the resulting address there must be at most enough
+     room for (sizeof(buf) - 1) or 4 bytes.  */
+  memcpy (&buf[X()] + 1, p, 4);
+
+  memcpy (&buf[X()] + 1, p, 5);  /* { dg-warning "writing" } */
+
+  memcpy (&buf[X()] + 5, p, 6);  /* { dg-warning "writing" } */
+
+  /* The negative constant offset below must have no effect on the maximum
+     size of the buffer.  */
+  memcpy (&buf[X()] - 1, p, 5);
+
+  memcpy (&buf[X()] - 1, p, 6);  /* { dg-warning "writing" } */
+
+  memcpy (&buf[X()] - 5, p, 6);  /* { dg-warning "writing" } */
+
+  memcpy (&buf[X()] + X(), p, 5);
+
+  memcpy (&buf[X()] + X(), p, 6);  /* { dg-warning "writing" } */
+
+  memcpy (&buf[X()] - X(), p, 5);
+
+  memcpy (&buf[X()] - X(), p, 6);  /* { dg-warning "writing" } */
+}
+
+static inline size_t
+range (size_t min, size_t max)
+{
+  const size_t val = random_value ();
+  return val < min || max < val ? min : val;
+}
+
+/* Test memcpy with a variable offset not known to be in any range
+   and with a number of bytes bounded by a known range.  */
+
+void test_var_range (void *dst, const void *p)
+{
+  char buf[5];
+
+  memcpy (&buf[X()], p, range (0, 5));
+  memcpy (&buf[X()], p, range (1, 5));
+  memcpy (&buf[X()], p, range (2, 5));
+  memcpy (&buf[X()], p, range (3, 5));
+  memcpy (&buf[X()], p, range (4, 5));
+
+  memcpy (&buf[X()], p, range (6, 7));  /* { dg-warning "writing" } */
+
+  const size_t max = SIZE_MAX;
+  memcpy (&buf[X()], p, range (max - 1, max));  /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */
+
+  memcpy (dst, p, range (max / 2 + 1, max));  /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */
+}
+
+/* Return an ingeger in the range [MIN, MASK].  Use bitwise operations
+   rather than inequality to avoid relying on optimization passes
+   beyond Early Value Range Propagation that __builtin_object_size
+   doesn't make use of (yet).  */
+
+static inline size_t
+simple_range (unsigned min, unsigned mask)
+{
+  return ((unsigned)random_value () & mask) | (min & mask);
+}
+
+/* For brevity. */
+#define R(min, max) simple_range (min, max)
+
+void test_range_auto (const void *p)
+{
+  char buf[5];
+
+  memcpy (buf + R (0, 1), p, 6);  /* { dg-warning "writing" } */
+
+  /* Some of these could be diagnosed as an extension because they would
+     overflow when (if) the non-const index were sufficiently large.
+     The challenge is distinguishing a range where a variable is likely
+     to exceed the minimum required for the overflow to occur from one
+     where it isn't so likely.  */
+  memcpy (buf + R (0, 1), p, 5);
+
+  memcpy (buf + R (0, 1), p, 4);
+
+  memcpy (buf + R (0, 2), p, 3);
+
+  memcpy (buf + R (0, 3), p, 2);
+
+  memcpy (buf + R (0, 7), p, 1);
+
+  memcpy (buf + R (0, 5), p, 0);
+
+  /* The offset is known to be at least 1 so the size of the object
+     is at most 4.  */
+  memcpy (buf + R (1, 2), p, 3);
+
+  memcpy (buf + R (1, 3), p, 3);
+
+  memcpy (buf + R (1, 2), p, 4);
+
+  memcpy (buf + R (1, 3) + 1, p, 4);  /* { dg-warning "writing" } */
+
+  memcpy (buf + R (1, 3), p, 5);  /* { dg-warning "writing" } */
+
+  memcpy (buf + R (2, 3), p, 2);
+
+  memcpy (buf + R (2, 3), p, 3);
+
+  memcpy (buf + R (2, 3) + 1, p, 3);  /* { dg-warning "writing" } */
+
+  memcpy (buf + R (2, 3), p, 4);  /* { dg-warning "writing" } */
+
+  memcpy (buf + R (3, 4), p, 2);
+
+  memcpy (buf + R (3, 7), p, 3);  /* { dg-warning "writing" } */
+
+  memcpy (buf + R (3, 7), p, 9);  /* { dg-warning "writing" } */
+
+  memcpy (&buf[R (0, 1)] + 1, p, 4);
+
+  memcpy (&buf[R (0, 2)] + 1, p, 4);
+
+  memcpy (&buf[R (0, 3)] + 1, p, 4);
+
+  memcpy (&buf[R (0, 4)] + 1, p, 4);
+
+  memcpy (&buf[R (0, 5)] + 1, p, 4);
+
+  memcpy (&buf[R (0, 2)] + 2, p, 4);  /* { dg-warning "writing" } */
+
+  memcpy (&buf[R (0, 2)] - 2, p, 3);
+  memcpy (&buf[R (0, 2)] - 2, p, 3);
+
+  memcpy (&buf[R (0, 2)] - 2, p, 3);
+
+  memcpy (&buf[R (5, 15)], p, 1);  /* { dg-warning "writing" } */
+
+  /* With the offset given by the two ranges below there is at most
+     1 byte left.  */
+  memcpy (buf + R (1, 2) + R (3, 4), p, 1);
+
+  memcpy (buf + R (1, 3) + R (3, 7), p, 2);   /* { dg-warning "writing" } */
+
+  /* Unfortunately, the following isn't handled quite right: only
+     the lower bound given by the first range is used, the second
+     one is disregarded.  */
+  memcpy (&buf [R (1, 2)] + R (3, 4), p, 2);   /* { dg-warning "writing" "index in range plus offset in range" { xfail *-*-* } } */
+
+  memcpy (buf + R (2, 3) + R (2, 3), p, 1);
+
+  memcpy (buf + R (2, 3) + R (2, 3), p, 2);   /* { dg-warning "writing" } */
+}
+
+void test_range_malloc (const void *p)
+{
+  char *buf = __builtin_malloc (5);
+
+  memcpy (buf + R (0, 1), p, 6);  /* { dg-warning "writing" } */
+
+  memcpy (buf + R (0, 1), p, 5);
+
+  memcpy (buf + R (0, 1), p, 4);
+
+  memcpy (buf + R (0, 2), p, 3);
+
+  memcpy (buf + R (0, 3), p, 2);
+
+  memcpy (buf + R (0, 4), p, 1);
+
+  memcpy (buf + R (0, 5), p, 0);
+}
+
+void test_range_schar (signed char i, const void *s)
+{
+  char a [130];
+
+  /* The range of I is [-128, 127] so the size of the destination below
+     is at most 2 (i.e., 130 - 128) bytes. */
+  memcpy (&a [130] + i, s, 2);
+
+  /* Strictly, the range of I below is [0, 127] because a negative value
+     would result in forming an invalid pointer, so the destination is at
+     most 2 bytes. */
+  memcpy (&a [i] + 128, s, 2);
+
+  /* Reset I's range just in case it gets set above.  */
+  i = random_value ();
+  memcpy (&a [130] + i, s, 3);   /* { dg-warning "writing" } */
+
+  i = random_value ();
+  memcpy (&a [i] + 128, s, 3);   /* { dg-warning "writing" } */
+}
+
+void test_range_uchar (unsigned char i, const void *s)
+{
+  char a [260];
+
+  /* The range of I is [0, 255] so the size of the destination below
+     is at most 2 (i.e., 260 - 258 + 0) bytes. */
+  memcpy (&a [258] + i, s, 2);
+
+  memcpy (&a [i] + 128, s, 2);
+
+  /* Reset I's range just in case it gets set above.  */
+  i = random_value ();
+  memcpy (&a [258] + i, s, 3);   /* { dg-warning "writing" } */
+
+  i = random_value ();
+  memcpy (&a [i] + 258, s, 3);   /* { dg-warning "writing" } */
+
+  i = random_value ();
+  memcpy (&a [259] + i, s, 2);   /* { dg-warning "writing" } */
+
+  i = random_value ();
+  memcpy (&a [i] + 259, s, 2);   /* { dg-warning "writing" } */
+
+  i = random_value ();
+  memcpy (&a [260] + i, s, 1);   /* { dg-warning "writing" } */
+
+  i = random_value ();
+  memcpy (&a [i] + 260, s, 1);   /* { dg-warning "writing" } */
+
+  i = random_value ();
+  memcpy (&a [260] + i, s, 0);
+
+  i = random_value ();
+  memcpy (&a [i] + 260, s, 0);
+}
+
+void test_range_int (int i, const void *s)
+{
+  const size_t max = (size_t)INT_MAX * 2 + 1;
+
+  char *a = __builtin_malloc (max);
+
+  memcpy (&a [max] + i, s, INT_MAX);
+  memcpy (&a [max] - i, s, INT_MAX);
+  /* &*/
+  memcpy (&a [max] + i, s, INT_MAX + (size_t)1);
+  memcpy (&a [max] - i, s, INT_MAX + (size_t)1); /* { dg-warning "writing" } */
+  memcpy (&a [max] + i, s, INT_MAX + (size_t)2); /* { dg-warning "writing" } */
+  memcpy (&a [max] - i, s, INT_MAX + (size_t)2); /* { dg-warning "writing" } */
+
+  char *end = &a [max];
+  memcpy (end + i, s, INT_MAX);
+  memcpy (end - i, s, INT_MAX);
+  /* &*/
+  memcpy (end + i, s, INT_MAX + (size_t)1);
+  memcpy (end - i, s, INT_MAX + (size_t)1); /* { dg-warning "writing" } */
+  memcpy (end + i, s, INT_MAX + (size_t)2); /* { dg-warning "writing" } */
+  memcpy (end - i, s, INT_MAX + (size_t)2); /* { dg-warning "writing" } */
+
+  memcpy (&a [i] + max, s, 1);
+}
+
+
+void test_range_ptrdiff_t (ptrdiff_t i, const void *s)
+{
+  const size_t max = PTRDIFF_MAX;
+
+  char *a = __builtin_malloc (max);
+
+  memcpy (&a [max] + i, s, max);
+
+  memcpy (&a [i] + max - 1, s, 1);
+}
+
+void test_range_size_t (size_t i, const void *s)
+{
+  const size_t diffmax = PTRDIFF_MAX;
+
+  char *a = __builtin_malloc (diffmax);
+
+  memcpy (&a [diffmax] + i, s, 0);
+  memcpy (&a [i] + diffmax, s, 0);
+
+  memcpy (&a [diffmax] + i, s, 1);  /* { dg-warning "writing" } */
+
+  memcpy (&a [i] + diffmax, s, 1);  /* { dg-warning "writing" } */
+}
+
+struct S {
+  int i;
+  char a7[7];
+  int b;
+};
+
+void test_range_member_array (struct S *s, const void *p)
+{
+  memcpy (s->a7 + R (0, 1), p, 6);
+
+  memcpy (s->a7 + R (0, 1), p, 7);
+
+  memcpy (s->a7 + R (0, 1), p, 8);  /* { dg-warning "writing" } */
+
+  memcpy (&s->a7 [R (0, 1)], p, 7);
+
+  memcpy (&s->a7 [R (1, 3)], p, 7);  /* { dg-warning "writing" } */
+}
diff --git a/gcc/testsuite/gcc.dg/builtin-stringop-chk-4.c b/gcc/testsuite/gcc.dg/builtin-stringop-chk-4.c
new file mode 100644
index 0000000..7979a13
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/builtin-stringop-chk-4.c
@@ -0,0 +1,459 @@ 
+/* Test exercising buffer overflow warnings emitted for __*_chk builtins
+   in cases where the destination involves a non-constant offset into
+   an object of known size.  */
+/* { dg-do compile } */
+/* { dg-options "-O2 -ftrack-macro-expansion=0" } */
+
+#define INT_MAX      __INT_MAX__
+#define PTRDIFF_MAX  __PTRDIFF_MAX__
+#define SIZE_MAX     __SIZE_MAX__
+
+typedef __PTRDIFF_TYPE__ ptrdiff_t;
+typedef __SIZE_TYPE__    size_t;
+
+static const size_t ssize_max = SIZE_MAX / 2;
+static const size_t size_max = SIZE_MAX;
+
+extern signed char    schar_val;
+extern signed short   sshrt_val;
+extern signed int     sint_val;
+extern signed long    slong_val;
+extern unsigned char  uchar_val;
+extern unsigned short ushrt_val;
+extern unsigned int   uint_val;
+extern unsigned long  ulong_val;
+
+#define memcpy(d, s, n) (memcpy ((d), (s), (n)), sink ((d)))
+extern void* (memcpy)(void*, const void*, size_t);
+
+#define mempcpy(d, s, n) (mempcpy ((d), (s), (n)), sink ((d)))
+extern void* (mempcpy)(void*, const void*, size_t);
+
+#define memset(d, c, n) (memset ((d), (c), (n)), sink ((d)))
+extern void* (memset)(void*, int, size_t);
+
+#define bzero(d, n) (bzero ((d), (n)), sink ((d)))
+extern void (bzero)(void*, size_t);
+
+#define strcat(d, s) (strcat ((d), (s)), sink ((d)))
+extern char* (strcat)(char*, const char*);
+
+#define strncat(d, s, n) (strncat ((d), (s), (n)), sink ((d)))
+extern char* (strncat)(char*, const char*, size_t);
+
+#define strcpy(d, s) (strcpy ((d), (s)), sink ((d)))
+extern char* (strcpy)(char*, const char*);
+
+#define strncpy(d, s, n) (strncpy ((d), (s), (n)), sink ((d)))
+extern char* (strncpy)(char*, const char*, size_t);
+
+void sink (void*);
+
+/* Function to "generate" a random number each time it's called.  Declared
+   (but not defined) and used to prevent GCC from making assumptions about
+   their values based on the variables uses in the tested expressions.  */
+size_t random_unsigned_value (void);
+ptrdiff_t random_signed_value (void);
+
+/* Return a random unsigned value between MIN and MAX.  */
+
+static inline size_t
+unsigned_range (size_t min, size_t max)
+{
+  const size_t val = random_unsigned_value ();
+  return val < min || max < val ? min : val;
+}
+
+/* Return a random signed value between MIN and MAX.  */
+
+static inline ptrdiff_t
+signed_range (ptrdiff_t min, ptrdiff_t max)
+{
+  const ptrdiff_t val = random_signed_value ();
+  return val < min || max < val ? min : val;
+}
+
+/* For brevity.  */
+#define UR(min, max)   unsigned_range (min, max)
+#define SR(min, max)   signed_range (min, max)
+
+/* UReturn a pointer to constant string whose length is at least MINLEN
+   and at most 10.  */
+static inline const char*
+string_range (size_t minlen)
+{
+  static const char str[] = "0123456789";
+
+  const size_t len = unsigned_range (minlen, sizeof str - 1);
+
+  switch (len)
+    {
+    case 10: return "0123456789";
+    case  9: return "012345678";
+    case  8: return "01234567";
+    case  7: return "0123456";
+    case  6: return "012345";
+    case  5: return "01234";
+    case  4: return "0123";
+    case  3: return "012";
+    case  2: return "01";
+    case  1: return "0";
+    case  0: return "";
+    }
+}
+
+#define S(minlen)   string_range (minlen)
+
+/* Test memcpy with a number of bytes bounded by a known range.  */
+
+void test_memcpy_range (void *d, const void *s)
+{
+  char buf[5];
+
+  memcpy (buf, s, UR (0, 5));
+  memcpy (buf, s, UR (1, 5));
+  memcpy (buf, s, UR (2, 5));
+  memcpy (buf, s, UR (3, 5));
+  memcpy (buf, s, UR (4, 5));
+
+  memcpy (buf, s, UR (6, 7));  /* { dg-warning "writing between 6 and 7 bytes into a region of size 5 overflows the destination" } */
+
+  memcpy (buf, s, UR (ssize_max, size_max));   /* { dg-warning "writing between \[0-9\]+ and \[0-9\]+ bytes into a region of size 5 overflows the destination" } */
+  memcpy (buf, s, UR (ssize_max + 1, size_max));  /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */
+  memcpy (buf, s, UR (size_max - 1, size_max));  /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */
+
+  /* Exercise memcpy into a destination of unknown size with excessive
+     number of bytes.  */
+  memcpy (d, s, UR (ssize_max, size_max));
+  memcpy (d, s, UR (ssize_max + 1, size_max));   /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */
+
+  memcpy (buf, s, SR (-1, 1));
+  memcpy (buf, s, SR (-3, 2));
+  memcpy (buf, s, SR (-5, 3));
+  memcpy (buf, s, SR (-7, 4));
+  memcpy (buf, s, SR (-9, 5));
+  memcpy (buf, s, SR (-11, 6));
+
+  memcpy (d, s, SR (-1, 1));
+  memcpy (d, s, SR (-3, 2));
+  memcpy (d, s, SR (-5, 3));
+  memcpy (d, s, SR (-7, 4));
+  memcpy (d, s, SR (-9, 5));
+  memcpy (d, s, SR (-11, 6));
+
+  memcpy (buf, s, SR (-2, -1));   /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */
+  memcpy (d, s, SR (-2, -1));   /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */
+
+  /* Even though the following calls are bounded by the range of N's
+     type they must not cause a warning for obvious reasons.  */
+  memcpy (buf, s, schar_val);
+  memcpy (buf, s, sshrt_val);
+  memcpy (buf, s, sint_val);
+  memcpy (buf, s, slong_val);
+
+  memcpy (buf, s, uchar_val);
+  memcpy (buf, s, ushrt_val);
+  memcpy (buf, s, uint_val);
+  memcpy (buf, s, ulong_val);
+
+  memcpy (buf, s, schar_val + 1);
+  memcpy (buf, s, sshrt_val + 2);
+  memcpy (buf, s, sint_val + 3);
+  memcpy (buf, s, slong_val + 4);
+
+  memcpy (d, s, uchar_val + 5);
+  memcpy (d, s, ushrt_val + 6);
+  memcpy (d, s, uint_val + 7);
+  memcpy (d, s, ulong_val + 8);
+
+  memcpy (d, s, schar_val);
+  memcpy (d, s, sshrt_val);
+  memcpy (d, s, sint_val);
+  memcpy (d, s, slong_val);
+
+  memcpy (d, s, uchar_val);
+  memcpy (d, s, ushrt_val);
+  memcpy (d, s, uint_val);
+  memcpy (d, s, ulong_val);
+
+  memcpy (d, s, schar_val + 1);
+  memcpy (d, s, sshrt_val + 2);
+  memcpy (d, s, sint_val + 3);
+  memcpy (d, s, slong_val + 4);
+
+  memcpy (d, s, uchar_val + 5);
+  memcpy (d, s, ushrt_val + 6);
+  memcpy (d, s, uint_val + 7);
+  memcpy (d, s, ulong_val + 8);
+}
+
+/* Test mempcpy with a number of bytes bounded by a known range.  */
+
+void test_mempcpy_range (void *d, const void *s)
+{
+  char buf[5];
+
+  mempcpy (buf, s, UR (0, 5));
+  mempcpy (buf, s, UR (1, 5));
+  mempcpy (buf, s, UR (2, 5));
+  mempcpy (buf, s, UR (3, 5));
+  mempcpy (buf, s, UR (4, 5));
+
+  mempcpy (buf, s, UR (6, 7));  /* { dg-warning "writing between 6 and 7 bytes into a region of size 5 overflows the destination" } */
+
+  mempcpy (buf, s, UR (6, 7));  /* { dg-warning "writing between 6 and 7 bytes into a region of size 5 overflows the destination" } */
+
+  mempcpy (buf, s, UR (ssize_max, size_max));   /* { dg-warning "writing between \[0-9\]+ and \[0-9\]+ bytes into a region of size 5 overflows the destination" } */
+  mempcpy (buf, s, UR (ssize_max + 1, size_max));  /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */
+  mempcpy (buf, s, UR (size_max - 1, size_max));  /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */
+
+  /* Exercise mempcpy into a destination of unknown size with excessive
+     number of bytes.  */
+  mempcpy (d, s, UR (ssize_max, size_max));
+  mempcpy (d, s, UR (ssize_max + 1, size_max));   /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */
+}
+
+/* Test memset with a number of bytes bounded by a known range.  */
+
+void test_memset_range (void *d)
+{
+  char buf[5];
+
+  memset (buf, 0, UR (0, 5));
+  memset (buf, 0, UR (1, 5));
+  memset (buf, 0, UR (2, 5));
+  memset (buf, 0, UR (3, 5));
+  memset (buf, 0, UR (4, 5));
+
+  memset (buf, 0, UR (6, 7));  /* { dg-warning "writing between 6 and 7 bytes into a region of size 5 overflows the destination" } */
+
+  memset (buf, 0, UR (6, 7));  /* { dg-warning "writing between 6 and 7 bytes into a region of size 5 overflows the destination" } */
+
+  memset (buf, 0, UR (ssize_max, size_max));   /* { dg-warning "writing between \[0-9\]+ and \[0-9\]+ bytes into a region of size 5 overflows the destination" } */
+  memset (buf, 0, UR (ssize_max + 1, size_max));  /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */
+  memset (buf, 0, UR (size_max - 1, size_max));  /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */
+
+  /* Exercise memset into a destination of unknown size with excessive
+     number of bytes.  */
+  memset (d, 0, UR (ssize_max, size_max));
+  memset (d, 0, UR (ssize_max + 1, size_max));   /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */
+}
+
+/* Test bzero with a number of bytes bounded by a known range.  */
+
+void test_bzero_range (void *d)
+{
+  char buf[5];
+
+  bzero (buf, UR (0, 5));
+  bzero (buf, UR (1, 5));
+  bzero (buf, UR (2, 5));
+  bzero (buf, UR (3, 5));
+  bzero (buf, UR (4, 5));
+
+  bzero (buf, UR (6, 7));  /* { dg-warning "writing between 6 and 7 bytes into a region of size 5 overflows the destination" } */
+
+  bzero (buf, UR (6, 7));  /* { dg-warning "writing between 6 and 7 bytes into a region of size 5 overflows the destination" } */
+
+  bzero (buf, UR (ssize_max, size_max));   /* { dg-warning "writing between \[0-9\]+ and \[0-9\]+ bytes into a region of size 5 overflows the destination" } */
+  bzero (buf, UR (ssize_max + 1, size_max));  /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */
+  bzero (buf, UR (size_max - 1, size_max));  /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */
+
+  /* Exercise bzero into a destination of unknown size with excessive
+     number of bytes.  */
+  bzero (d, UR (ssize_max, size_max));
+  bzero (d, UR (ssize_max + 1, size_max));   /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */
+}
+
+/* Test strcat with an argument referencing a non-constant string of
+   lengths in a known range.  */
+
+void test_strcat_range (void)
+{
+  char buf[5] = "";
+
+  strcat (buf, S (0));
+  strcat (buf, S (1));
+  strcat (buf, S (2));
+  strcat (buf, S (3));
+  strcat (buf, S (4));
+  strcat (buf, S (5));   /* { dg-warning "writing 6 bytes into a region of size 5 " } */
+
+  {
+    /* The implementation of the warning isn't smart enough to determine
+       the length of the string in the buffer so it assumes it's empty
+       and issues the warning basically for the same cases as strcat.  */
+    char buf2[5] = "12";
+    strcat (buf2, S (4));   /* { dg-warning "writing 5 bytes into a region of size 3" "strcat to a non-empty string" { xfail *-*-* } } */
+  }
+}
+
+/* Test strcpy with a non-constant source string of length in a known
+   range.  */
+
+void test_strcpy_range (void)
+{
+  char buf[5];
+
+  strcpy (buf, S (0));
+  strcpy (buf, S (1));
+  strcpy (buf, S (2));
+  strcpy (buf, S (4));
+  strcpy (buf, S (5));   /* { dg-warning "writing 6 bytes into a region of size 5 " } */
+  strcpy (buf, S (6));   /* { dg-warning "writing 7 bytes into a region of size 5 " } */
+  strcpy (buf, S (7));   /* { dg-warning "writing 8 bytes into a region of size 5 " } */
+  strcpy (buf, S (8));   /* { dg-warning "writing 9 bytes into a region of size 5 " } */
+  strcpy (buf, S (9));   /* { dg-warning "writing 10 bytes into a region of size 5 " } */
+  strcpy (buf, S (10));   /* { dg-warning "writing 11 bytes into a region of size 5 " } */
+}
+
+/* Test strncat with an argument referencing a non-constant string of
+   lengths in a known range.  */
+
+void test_strncat_range (void)
+{
+  char buf[5] = "";
+
+  strncat (buf, S (0), 0);
+  strncat (buf, S (0), 1);
+  strncat (buf, S (0), 2);
+  strncat (buf, S (0), 3);
+  strncat (buf, S (0), 4);
+  /* Strncat always appends a terminating null after copying the N
+     characters so the following triggers a warning pointing out
+     that specifying sizeof(buf) as the upper bound may cause
+     the nul to overflow the destination.  */
+  strncat (buf, S (0), 5);   /* { dg-warning "specified bound 5 equals the size of the destination" } */
+  strncat (buf, S (0), 6);   /* { dg-warning "specified bound 6 exceeds the size 5 of the destination" } */
+
+  strncat (buf, S (1), 0);
+  strncat (buf, S (1), 1);
+  strncat (buf, S (1), 2);
+  strncat (buf, S (1), 3);
+  strncat (buf, S (1), 4);
+  strncat (buf, S (1), 5);   /* { dg-warning "specified bound 5 equals the size of the destination" } */
+  strncat (buf, S (1), 6);   /* { dg-warning "specified bound 6 exceeds the size 5 of the destination" } */
+  strncat (buf, S (2), 6);   /* { dg-warning "specified bound 6 exceeds the size 5 of the destination" } */
+
+  /* The following could just as well say "writing 6 bytes into a region
+     of size 5.  Either would be correct and probably equally as clear
+     in this case.  But when the length of the source string is not known
+     at all then the bound warning seems clearer.  */
+  strncat (buf, S (5), 6);   /* { dg-warning "specified bound 6 exceeds the size 5 of the destination " } */
+  strncat (buf, S (7), 6);   /* { dg-warning "specified bound 6 exceeds the size 5 of the destination" } */
+
+  {
+    /* The implementation of the warning isn't smart enough to determine
+       the length of the string in the buffer so it assumes it's empty
+       and issues the warning basically for the same cases as strncpy.  */
+    char buf2[5] = "12";
+    strncat (buf2, S (4), 4);   /* { dg-warning "writing 5 bytes into a region of size 3" "strncat to a non-empty string" { xfail *-*-* } } */
+  }
+}
+
+/* Test strncat_chk with an argument referencing a non-constant string
+   of lengths in a known range.  */
+
+void test_strncat_chk_range (char *d)
+{
+  char buf[5] = "";
+
+#define strncat_chk(d, s, n) \
+  __builtin___strncat_chk ((d), (s), (n), __builtin_object_size (d, 1));
+
+  strncat_chk (buf, S (0), 1);
+  strncat_chk (buf, S (0), 2);
+  strncat_chk (buf, S (0), 3);
+  strncat_chk (buf, S (0), 4);
+  strncat_chk (buf, S (0), 5);   /* { dg-warning "specified bound 5 equals the size of the destination " } */
+
+  strncat_chk (buf, S (5), 1);
+  strncat_chk (buf, S (5), 2);
+  strncat_chk (buf, S (5), 3);
+  strncat_chk (buf, S (5), 4);
+  strncat_chk (buf, S (5), 5);   /* { dg-warning "specified bound 5 equals the size of the destination " } */
+
+  strncat_chk (buf, S (5), size_max);   /* { dg-warning "specified bound \[0-9\]+ exceeds the size 5 of the destination " } */
+
+  strncat_chk (d, S (5), size_max);   /* { dg-warning "specified bound \[0-9\]+ exceeds maximum object size " } */
+}
+
+/* Test strncpy with a non-constant source string of length in a known
+   range and a constant number of bytes.  */
+
+void test_strncpy_string_range (char *d)
+{
+  char buf[5];
+
+  strncpy (buf, S (0), 0);
+  strncpy (buf, S (0), 1);
+  strncpy (buf, S (0), 2);
+  strncpy (buf, S (0), 3);
+  strncpy (buf, S (0), 4);
+  strncpy (buf, S (0), 5);
+  strncpy (buf, S (0), 6);   /* { dg-warning "writing 6 bytes into a region of size 5 " } */
+
+  strncpy (buf, S (6), 4);
+  strncpy (buf, S (7), 5);
+  strncpy (buf, S (8), 6);   /* { dg-warning "writing 6 bytes into a region of size 5 " } */
+
+  strncpy (buf, S (1), ssize_max - 1);   /* { dg-warning "writing \[0-9\]+ bytes into a region of size 5" } */
+  strncpy (buf, S (2), ssize_max);   /* { dg-warning "writing \[0-9\]+ bytes into a region of size 5" } */
+  strncpy (buf, S (3), ssize_max + 1);   /* { dg-warning "specified size \[0-9\]+ exceeds maximum object size" } */
+  strncpy (buf, S (4), size_max);   /* { dg-warning "specified size \[0-9\]+ exceeds maximum object size" } */
+
+  /* Exercise strncpy into a destination of unknown size with a valid
+     and invalid constant number of bytes.  */
+  strncpy (d, S (1), ssize_max - 1);
+  strncpy (d, S (2), ssize_max);
+  strncpy (d, S (3), ssize_max + 1);   /* { dg-warning "specified size \[0-9\]+ exceeds maximum object size" } */
+  strncpy (d, S (4), size_max);   /* { dg-warning "specified size \[0-9\]+ exceeds maximum object size" } */
+}
+
+/* Test strncpy with a non-constant source string of length in a known
+   range and a non-constant number of bytes also in a known range.  */
+
+void test_strncpy_string_count_range (char *d, const char *s)
+{
+  char buf[5];
+
+  strncpy (buf, S (0), UR (0, 1));
+  strncpy (buf, S (0), UR (0, 2));
+  strncpy (buf, S (0), UR (0, 3));
+  strncpy (buf, S (0), UR (0, 4));
+  strncpy (buf, S (0), UR (0, 5));
+  strncpy (buf, S (0), UR (0, 6));
+  strncpy (buf, S (0), UR (1, 6));
+  strncpy (buf, S (0), UR (2, 6));
+  strncpy (buf, S (0), UR (3, 6));
+  strncpy (buf, S (0), UR (4, 6));
+  strncpy (buf, S (0), UR (5, 6));
+
+  strncpy (buf, S (9), UR (0, 1));
+  strncpy (buf, S (8), UR (0, 2));
+  strncpy (buf, S (7), UR (0, 3));
+  strncpy (buf, S (6), UR (0, 4));
+  strncpy (buf, S (8), UR (0, 5));
+  strncpy (buf, S (7), UR (0, 6));
+  strncpy (buf, S (6), UR (1, 6));
+  strncpy (buf, S (5), UR (2, 6));
+  strncpy (buf, S (9), UR (3, 6));
+  strncpy (buf, S (8), UR (4, 6));
+  strncpy (buf, S (7), UR (5, 6));
+
+  strncpy (buf, S (0), UR (6, 7));   /* { dg-warning "writing between 6 and 7 bytes into a region of size 5 " } */
+  strncpy (buf, S (1), UR (7, 8));   /* { dg-warning "writing between 7 and 8 bytes into a region of size 5 " } */
+  strncpy (buf, S (2), UR (ssize_max, ssize_max + 1));   /* { dg-warning "writing between \[0-9\]+ and \[0-9\]+ bytes into a region of size 5 " } */
+
+  strncpy (buf, S (2), UR (ssize_max + 1, ssize_max + 2));   /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */
+
+  /* Exercise strncpy into a destination of unknown size  with a valid
+     and invalid constant number of bytes.  */
+  strncpy (d, S (0), UR (5, 6));
+  strncpy (d, S (1), UR (6, 7));
+  strncpy (d, S (2), UR (7, 8));
+
+  strncpy (d, S (3), UR (ssize_max, ssize_max + 1));
+
+  strncpy (d, S (4), UR (ssize_max + 1, ssize_max + 2));   /* { dg-warning "specified size between \[0-9\]+ and \[0-9\]+ exceeds maximum object size" } */
+}
diff --git a/gcc/testsuite/gcc.dg/builtin-strncat-chk-1.c b/gcc/testsuite/gcc.dg/builtin-strncat-chk-1.c
index 44677f1..daff680 100644
--- a/gcc/testsuite/gcc.dg/builtin-strncat-chk-1.c
+++ b/gcc/testsuite/gcc.dg/builtin-strncat-chk-1.c
@@ -24,15 +24,15 @@  test (int arg, ...)
   *p = 0;
   strncat (p, "abcdefghi", 10);
   *p = 0;
-  strncat (p, "abcdefghij", 10); /* { dg-warning "will always overflow" } */
+  strncat (p, "abcdefghij", 10); /* { dg-warning "writing 11 bytes into a region of size 10 overflows the destination" } */
   *p = 0;
   strncat (p, "abcdefgh", 11);
   *p = 0;
-  strncat (p, "abcdefghijkl", 11); /* { dg-warning "will always overflow" } */
+  strncat (p, "abcdefghijkl", 11); /* { dg-warning "specified bound 11 exceeds the size 10 of the destination" } */
   *p = 0;
   strncat (p, q, 9);
   *p = 0;
-  strncat (p, q, 10); /* { dg-warning "might overflow" } */
+  strncat (p, q, 10); /* { dg-warning "specified bound 10 equals the size of the destination" } */
   *p = 0;
-  strncat (p, q, 11); /* { dg-warning "might overflow" } */
+  strncat (p, q, 11); /* { dg-warning "specified bound 11 exceeds the size 10 of the destination" } */
 }
diff --git a/gcc/testsuite/gcc.dg/memcpy-2.c b/gcc/testsuite/gcc.dg/memcpy-2.c
index 24464abd..7f839d2 100644
--- a/gcc/testsuite/gcc.dg/memcpy-2.c
+++ b/gcc/testsuite/gcc.dg/memcpy-2.c
@@ -7,7 +7,7 @@  typedef __SIZE_TYPE__ size_t;
 extern inline __attribute__((gnu_inline, always_inline, artificial)) void *
 memcpy (void *__restrict dest, const void *__restrict src, size_t len)
 {
-  return __builtin___memcpy_chk (dest, /* { dg-warning "will always overflow destination buffer" } */
+  return __builtin___memcpy_chk (dest, /* { dg-warning "writing" } */
 				 src, len, __builtin_object_size (dest, 0));
 }
 
diff --git a/gcc/testsuite/gcc.dg/pr40340-1.c b/gcc/testsuite/gcc.dg/pr40340-1.c
index aae84c6..78540a2 100644
--- a/gcc/testsuite/gcc.dg/pr40340-1.c
+++ b/gcc/testsuite/gcc.dg/pr40340-1.c
@@ -20,5 +20,5 @@  main (void)
   return 0;
 }
 
-/* { dg-warning "will always overflow destination buffer" "" { target *-*-* } 10 } */
+/* { dg-warning "writing" "" { target *-*-* } 10 } */
 /* { dg-message "file included" "In file included" { target *-*-* } 0 } */
diff --git a/gcc/testsuite/gcc.dg/pr40340-2.c b/gcc/testsuite/gcc.dg/pr40340-2.c
index a0d6e084..1dc21d1 100644
--- a/gcc/testsuite/gcc.dg/pr40340-2.c
+++ b/gcc/testsuite/gcc.dg/pr40340-2.c
@@ -12,5 +12,5 @@  main (void)
   return 0;
 }
 
-/* { dg-warning "will always overflow destination buffer" "" { target *-*-* } 10 } */
+/* { dg-warning "writing" "" { target *-*-* } 10 } */
 /* { dg-message "file included" "In file included" { target *-*-* } 0 } */
diff --git a/gcc/testsuite/gcc.dg/pr40340-5.c b/gcc/testsuite/gcc.dg/pr40340-5.c
index f50514c..e517147 100644
--- a/gcc/testsuite/gcc.dg/pr40340-5.c
+++ b/gcc/testsuite/gcc.dg/pr40340-5.c
@@ -13,5 +13,5 @@  main (void)
   return 0;
 }
 
-/* { dg-warning "will always overflow destination buffer" "" { target *-*-* } 10 } */
+/* { dg-warning "writing" "" { target *-*-* } 10 } */
 /* { dg-message "file included" "In file included" { target *-*-* } 0 } */
diff --git a/gcc/testsuite/gcc.dg/torture/Wsizeof-pointer-memaccess1.c b/gcc/testsuite/gcc.dg/torture/Wsizeof-pointer-memaccess1.c
index 7ce9eae..b5a59f4 100644
--- a/gcc/testsuite/gcc.dg/torture/Wsizeof-pointer-memaccess1.c
+++ b/gcc/testsuite/gcc.dg/torture/Wsizeof-pointer-memaccess1.c
@@ -710,4 +710,4 @@  f4 (char *x, char **y, int z, char w[64])
   return z;
 }
 
-/* { dg-prune-output "\[\n\r\]*will always overflow\[\n\r\]*" } */
+/* { dg-prune-output "\[\n\r\]*writing\[\n\r\]*" } */
diff --git a/gcc/testsuite/gcc.dg/torture/pr71132.c b/gcc/testsuite/gcc.dg/torture/pr71132.c
index 2991718..2544eb1 100644
--- a/gcc/testsuite/gcc.dg/torture/pr71132.c
+++ b/gcc/testsuite/gcc.dg/torture/pr71132.c
@@ -1,4 +1,9 @@ 
 /* { dg-do compile } */
+/* { dg-additional-options "-Wno-stringop-overflow" } */
+/* The loop below writes past the end of the global object a.
+   When the loop is transformed into a call to memcpy the buffer
+   overflow is detected and diagnosed by the -Wstringop-overflow
+   option enabled by default.  */
 
 typedef unsigned size_t;
 struct {