diff mbox series

move more code to access warning pass

Message ID 63e7fa68-42f3-ef49-f5f7-a3f2640efbbb@gmail.com
State New
Headers show
Series move more code to access warning pass | expand

Commit Message

Martin Sebor Aug. 5, 2021, 10 p.m. UTC
As I mentioned in the description of the access warning pass when
I submitted it in July(*), I planned to change the -Wstringop-xxx
code in the pass to run on the GIMPLE IL instead of on trees in
builtins.c (and elsewhere).  The attached patch implements this
change along with moving more warning code from builtins.c and
calls.c into the pass source.

The changes are mostly mechanical but I should explain one aspect
that might draw attention: since some of the warning functions are
still called from outside the pass with tree arguments, I made them
templates parameterized on the type of the argument: either gimple*
or tree, and provided overloads for each(**).  I expect this to be
a transient solution until remaining callers that pass in trees are
moved into the new pass.  This might take a bit of effort and time
and involve more churn than feels appropriate for a single patch.

Tested on x86_64-linux and by building Glibc and GDB+Binutils with
no new warnings.

As the next steps I plan to:

* integrate the new pass with ranger and enable the pointer query
   caching to avoid repeatedly computing object sizes for statements
   involving related pointers
* move remaining warning code from builtins.c and calls.c (and
   possibly also gimple-fold.c) into the new pass (as much of it
   as possible
* investigate running a subset of the new pass early on during
   optimization in addition to late as it does now, to detect
   problems that are impossible to detect otherwise (i.e., split
   the pass into two stages similar to -Wuninitialized and
   -Wmaybe-uninitialized)
* investigate integrating the uninitialized predicate analyzer
   into the pass to help reduce false positives and perhaps also
   false negatives by enabling maybe-kinds of diagnostics for
   conditional code
* integrate -Warray-bounds into the pass (and remove it from vrp)

Martin

[*] https://gcc.gnu.org/pipermail/gcc-patches/2021-July/575377.html
[**] This was made easy by introducing overloads of functions like
get_location(gimple*) and get_location(tree) to return the location
of a GIMPLE statement or a tree decl or expression.

Comments

Richard Biener Aug. 6, 2021, 6:50 a.m. UTC | #1
On Fri, Aug 6, 2021 at 12:01 AM Martin Sebor via Gcc-patches
<gcc-patches@gcc.gnu.org> wrote:
>
> As I mentioned in the description of the access warning pass when
> I submitted it in July(*), I planned to change the -Wstringop-xxx
> code in the pass to run on the GIMPLE IL instead of on trees in
> builtins.c (and elsewhere).  The attached patch implements this
> change along with moving more warning code from builtins.c and
> calls.c into the pass source.
>
> The changes are mostly mechanical but I should explain one aspect
> that might draw attention: since some of the warning functions are
> still called from outside the pass with tree arguments, I made them
> templates parameterized on the type of the argument: either gimple*
> or tree, and provided overloads for each(**).  I expect this to be
> a transient solution until remaining callers that pass in trees are
> moved into the new pass.  This might take a bit of effort and time
> and involve more churn than feels appropriate for a single patch.

ICK.  I'll take your word that this is transitional only.

OK.

Thanks,
Richard.

> Tested on x86_64-linux and by building Glibc and GDB+Binutils with
> no new warnings.
>
> As the next steps I plan to:
>
> * integrate the new pass with ranger and enable the pointer query
>    caching to avoid repeatedly computing object sizes for statements
>    involving related pointers
> * move remaining warning code from builtins.c and calls.c (and
>    possibly also gimple-fold.c) into the new pass (as much of it
>    as possible
> * investigate running a subset of the new pass early on during
>    optimization in addition to late as it does now, to detect
>    problems that are impossible to detect otherwise (i.e., split
>    the pass into two stages similar to -Wuninitialized and
>    -Wmaybe-uninitialized)
> * investigate integrating the uninitialized predicate analyzer
>    into the pass to help reduce false positives and perhaps also
>    false negatives by enabling maybe-kinds of diagnostics for
>    conditional code
> * integrate -Warray-bounds into the pass (and remove it from vrp)
>
> Martin
>
> [*] https://gcc.gnu.org/pipermail/gcc-patches/2021-July/575377.html
> [**] This was made easy by introducing overloads of functions like
> get_location(gimple*) and get_location(tree) to return the location
> of a GIMPLE statement or a tree decl or expression.
diff mbox series

Patch

gcc/ChangeLog:

	* builtins.c (expand_builtin_memchr): Move to gimple-ssa-warn-access.cc.
	(expand_builtin_strcat): Same.
	(expand_builtin_stpncpy): Same.
	(expand_builtin_strncat): Same.
	(check_read_access): Same.
	(check_memop_access): Same.
	(expand_builtin_strlen): Move checks to gimple-ssa-warn-access.cc.
	(expand_builtin_strnlen): Same.
	(expand_builtin_memcpy): Same.
	(expand_builtin_memmove): Same.
	(expand_builtin_mempcpy): Same.
	(expand_builtin_strcpy): Same.
	(expand_builtin_strcpy_args): Same.
	(expand_builtin_stpcpy_1): Same.
	(expand_builtin_strncpy): Same.
	(expand_builtin_memset): Same.
	(expand_builtin_bzero): Same.
	(expand_builtin_strcmp): Same.
	(expand_builtin_strncmp): Same.
	(expand_builtin): Remove handlers.
	(fold_builtin_strlen): Add a comment.
	* builtins.h (check_access): Move to gimple-ssa-warn-access.cc.
	* calls.c (maybe_warn_nonstring_arg): Same.
	* gimple-fold.c (gimple_fold_builtin_strcpy): Pass argument to callee.
	(gimple_fold_builtin_stpcpy): Same.
	* gimple-ssa-warn-access.cc (has_location): New function.
	(get_location): Same.
	(get_callee_fndecl): Same.
	(call_nargs): Same.
	(call_arg): Same.
	(warn_string_no_nul): Define.
	(unterminated_array): Same.
	(check_nul_terminated_array): Same.
	(maybe_warn_nonstring_arg): Same.
	(maybe_warn_for_bound): Same.
	(warn_for_access): Same.
	(check_access): Same.
	(check_memop_access): Same.
	(check_read_access): Same.
	(warn_dealloc_offset): Use helper functions.
	(maybe_emit_free_warning): Same.
	(class pass_waccess): Add members.
	(check_strcat): New function.
	(check_strncat): New function.
	(check_stxcpy): New function.
	(check_stxncpy): New function.
	(check_strncmp): New function.
	(pass_waccess::check_builtin): New function.
	(pass_waccess::check): Call it.
	* gimple-ssa-warn-access.h (warn_string_no_nul): Move here from
	builtins.h.
	(maybe_warn_for_bound): Same.
	(check_access): Same.
	(check_memop_access): Same.
	(check_read_access): Same.
	* pointer-query.h (struct access_data): Define a ctor overload.

gcc/testsuite/ChangeLog:

	* c-c++-common/Wsizeof-pointer-memaccess1.c: Also disable
	-Wstringop-overread.
	* c-c++-common/attr-nonstring-3.c: Adjust pattern of expected message.
	* gcc.dg/Warray-bounds-39.c: Add an xfail due to a known bug.
	* gcc.dg/Wstring-compare-3.c: Also disable -Wstringop-overread.
	* gcc.dg/attr-nonstring-2.c: Adjust pattern of expected message.
	* gcc.dg/attr-nonstring-4.c: Same.
	* gcc.dg/Wstringop-overread-6.c: New test.
	* gcc.dg/sso-14.c: Fix typos to avoid buffer overflow.

diff --git a/gcc/builtins.c b/gcc/builtins.c
index 2387b5d2a5d..d2be807f1d6 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -131,7 +131,6 @@  static rtx expand_builtin_va_copy (tree);
 static rtx inline_expand_builtin_bytecmp (tree, rtx);
 static rtx expand_builtin_strcmp (tree, rtx);
 static rtx expand_builtin_strncmp (tree, rtx, machine_mode);
-static rtx expand_builtin_memchr (tree, rtx);
 static rtx expand_builtin_memcpy (tree, rtx);
 static rtx expand_builtin_memory_copy_args (tree dest, tree src, tree len,
 					    rtx target, tree exp,
@@ -140,12 +139,9 @@  static rtx expand_builtin_memory_copy_args (tree dest, tree src, tree len,
 static rtx expand_builtin_memmove (tree, rtx);
 static rtx expand_builtin_mempcpy (tree, rtx);
 static rtx expand_builtin_mempcpy_args (tree, tree, tree, rtx, tree, memop_ret);
-static rtx expand_builtin_strcat (tree);
 static rtx expand_builtin_strcpy (tree, rtx);
 static rtx expand_builtin_strcpy_args (tree, tree, tree, rtx);
 static rtx expand_builtin_stpcpy (tree, rtx, machine_mode);
-static rtx expand_builtin_stpncpy (tree, rtx);
-static rtx expand_builtin_strncat (tree, rtx);
 static rtx expand_builtin_strncpy (tree, rtx);
 static rtx expand_builtin_memset (tree, rtx, machine_mode);
 static rtx expand_builtin_memset_args (tree, tree, tree, rtx, machine_mode, tree);
@@ -186,7 +182,6 @@  static rtx expand_builtin_memory_chk (tree, rtx, machine_mode,
 static void maybe_emit_chk_warning (tree, enum built_in_function);
 static void maybe_emit_sprintf_chk_warning (tree, enum built_in_function);
 static tree fold_builtin_object_size (tree, tree);
-static bool check_read_access (tree, tree, tree = NULL_TREE, int = 1);
 
 unsigned HOST_WIDE_INT target_newline;
 unsigned HOST_WIDE_INT target_percent;
@@ -2957,8 +2952,6 @@  expand_builtin_strlen (tree exp, rtx target,
     return NULL_RTX;
 
   tree src = CALL_EXPR_ARG (exp, 0);
-  if (!check_read_access (exp, src))
-    return NULL_RTX;
 
   /* If the length can be computed at compile-time, return it.  */
   if (tree len = c_strlen (src, 0))
@@ -3062,8 +3055,6 @@  expand_builtin_strnlen (tree exp, rtx target, machine_mode target_mode)
   if (!bound)
     return NULL_RTX;
 
-  check_read_access (exp, src, bound);
-
   location_t loc = UNKNOWN_LOCATION;
   if (EXPR_HAS_LOCATION (exp))
     loc = EXPR_LOCATION (exp);
@@ -3201,65 +3192,6 @@  determine_block_size (tree len, rtx len_rtx,
 			  GET_MODE_MASK (GET_MODE (len_rtx)));
 }
 
-/* A convenience wrapper for check_access above to check access
-   by a read-only function like puts.  */
-
-static bool
-check_read_access (tree exp, tree src, tree bound /* = NULL_TREE */,
-		   int ost /* = 1 */)
-{
-  if (!warn_stringop_overread)
-    return true;
-
-  if (bound && !useless_type_conversion_p (size_type_node, TREE_TYPE (bound)))
-    bound = fold_convert (size_type_node, bound);
-  access_data data (exp, access_read_only, NULL_TREE, false, bound, true);
-  compute_objsize (src, ost, &data.src);
-  return check_access (exp, /*dstwrite=*/ NULL_TREE, /*maxread=*/ bound,
-		       /*srcstr=*/ src, /*dstsize=*/ NULL_TREE, data.mode,
-		       &data);
-}
-
-/* Helper to determine and check the sizes of the source and the destination
-   of calls to __builtin_{bzero,memcpy,mempcpy,memset} calls.  EXP is the
-   call expression, DEST is the destination argument, SRC is the source
-   argument or null, and LEN is the number of bytes.  Use Object Size type-0
-   regardless of the OPT_Wstringop_overflow_ setting.  Return true on success
-   (no overflow or invalid sizes), false otherwise.  */
-
-static bool
-check_memop_access (tree exp, tree dest, tree src, tree size)
-{
-  /* For functions like memset and memcpy that operate on raw memory
-     try to determine the size of the largest source and destination
-     object using type-0 Object Size regardless of the object size
-     type specified by the option.  */
-  access_data data (exp, access_read_write);
-  tree srcsize = src ? compute_objsize (src, 0, &data.src) : NULL_TREE;
-  tree dstsize = compute_objsize (dest, 0, &data.dst);
-
-  return check_access (exp, size, /*maxread=*/NULL_TREE,
-		       srcsize, dstsize, data.mode, &data);
-}
-
-/* Validate memchr arguments without performing any expansion.
-   Return NULL_RTX.  */
-
-static rtx
-expand_builtin_memchr (tree exp, rtx)
-{
-  if (!validate_arglist (exp,
- 			 POINTER_TYPE, INTEGER_TYPE, INTEGER_TYPE, VOID_TYPE))
-    return NULL_RTX;
-
-  tree arg1 = CALL_EXPR_ARG (exp, 0);
-  tree len = CALL_EXPR_ARG (exp, 2);
-
-  check_read_access (exp, arg1, len, 0);
-
-  return NULL_RTX;
-}
-
 /* 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
@@ -3276,8 +3208,6 @@  expand_builtin_memcpy (tree exp, rtx target)
   tree src = CALL_EXPR_ARG (exp, 1);
   tree len = CALL_EXPR_ARG (exp, 2);
 
-  check_memop_access (exp, dest, src, len);
-
   return expand_builtin_memory_copy_args (dest, src, len, target, exp,
 					  /*retmode=*/ RETURN_BEGIN, false);
 }
@@ -3296,8 +3226,6 @@  expand_builtin_memmove (tree exp, rtx target)
   tree src = CALL_EXPR_ARG (exp, 1);
   tree len = CALL_EXPR_ARG (exp, 2);
 
-  check_memop_access (exp, dest, src, len);
-
   return expand_builtin_memory_copy_args (dest, src, len, target, exp,
 					  /*retmode=*/ RETURN_BEGIN, true);
 }
@@ -3334,8 +3262,6 @@  expand_builtin_mempcpy (tree exp, rtx target)
   /* 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_memop_access (exp, dest, src, len))
-    return NULL_RTX;
 
   return expand_builtin_mempcpy_args (dest, src, len,
 				      target, exp, /*retmode=*/ RETURN_END);
@@ -3511,36 +3437,6 @@  expand_movstr (tree dest, tree src, rtx target, memop_ret retmode)
   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)
-{
-  if (!validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE)
-      || !warn_stringop_overflow)
-    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 souce string is longer than
-     the destination object.  */
-  access_data data (exp, access_read_write, NULL_TREE, true,
-		    NULL_TREE, true);
-  const int ost = warn_stringop_overflow ? warn_stringop_overflow - 1 : 1;
-  compute_objsize (src, ost, &data.src);
-  tree destsize = compute_objsize (dest, ost, &data.dst);
-
-  check_access (exp, /*dstwrite=*/NULL_TREE, /*maxread=*/NULL_TREE,
-		src, destsize, data.mode, &data);
-
-  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
@@ -3555,29 +3451,7 @@  expand_builtin_strcpy (tree exp, rtx target)
   tree dest = CALL_EXPR_ARG (exp, 0);
   tree src = CALL_EXPR_ARG (exp, 1);
 
-  if (warn_stringop_overflow)
-    {
-      access_data data (exp, access_read_write, NULL_TREE, true,
-			NULL_TREE, true);
-      const int ost = warn_stringop_overflow ? warn_stringop_overflow - 1 : 1;
-      compute_objsize (src, ost, &data.src);
-      tree dstsize = compute_objsize (dest, ost, &data.dst);
-      check_access (exp, /*dstwrite=*/ NULL_TREE,
-		    /*maxread=*/ NULL_TREE, /*srcstr=*/ src,
-		    dstsize, data.mode, &data);
-    }
-
-  if (rtx ret = expand_builtin_strcpy_args (exp, dest, src, target))
-    {
-      /* Check to see if the argument was declared attribute nonstring
-	 and if so, issue a warning since at this point it's not known
-	 to be nul-terminated.  */
-      tree fndecl = get_callee_fndecl (exp);
-      maybe_warn_nonstring_arg (fndecl, exp);
-      return ret;
-    }
-
-  return NULL_RTX;
+  return expand_builtin_strcpy_args (exp, dest, src, target);
 }
 
 /* Helper function to do the actual work for expand_builtin_strcpy.  The
@@ -3587,19 +3461,8 @@  expand_builtin_strcpy (tree exp, rtx target)
    expand_builtin_strcpy.  */
 
 static rtx
-expand_builtin_strcpy_args (tree exp, tree dest, tree src, rtx target)
+expand_builtin_strcpy_args (tree, tree dest, tree src, rtx target)
 {
-  /* Detect strcpy calls with unterminated arrays..  */
-  tree size;
-  bool exact;
-  if (tree nonstr = unterminated_array (src, &size, &exact))
-    {
-      /* NONSTR refers to the non-nul terminated constant array.  */
-      warn_string_no_nul (EXPR_LOCATION (exp), exp, NULL, src, nonstr,
-			  size, exact);
-      return NULL_RTX;
-    }
-
   return expand_movstr (dest, src, target, /*retmode=*/ RETURN_BEGIN);
 }
 
@@ -3620,15 +3483,6 @@  expand_builtin_stpcpy_1 (tree exp, rtx target, machine_mode mode)
   dst = CALL_EXPR_ARG (exp, 0);
   src = CALL_EXPR_ARG (exp, 1);
 
-  if (warn_stringop_overflow)
-    {
-      access_data data (exp, access_read_write);
-      tree destsize = compute_objsize (dst, warn_stringop_overflow - 1,
-				       &data.dst);
-      check_access (exp, /*dstwrite=*/NULL_TREE, /*maxread=*/NULL_TREE,
-		    src, destsize, data.mode, &data);
-    }
-
   /* If return value is ignored, transform stpcpy into strcpy.  */
   if (target == const0_rtx && builtin_decl_implicit (BUILT_IN_STRCPY))
     {
@@ -3651,9 +3505,6 @@  expand_builtin_stpcpy_1 (tree exp, rtx target, machine_mode mode)
 	return expand_movstr (dst, src, target,
 			      /*retmode=*/ RETURN_END_MINUS_ONE);
 
-      if (lendata.decl)
-	warn_string_no_nul (EXPR_LOCATION (exp), exp, NULL, src, lendata.decl);
-
       lenp1 = size_binop_loc (loc, PLUS_EXPR, len, ssize_int (1));
       ret = expand_builtin_mempcpy_args (dst, src, lenp1,
 					 target, exp,
@@ -3715,30 +3566,6 @@  expand_builtin_stpcpy (tree exp, rtx target, machine_mode mode)
   return NULL_RTX;
 }
 
-/* Check a call EXP to the stpncpy built-in for validity.
-   Return NULL_RTX on both success and failure.  */
-
-static rtx
-expand_builtin_stpncpy (tree exp, rtx)
-{
-  if (!validate_arglist (exp,
-			 POINTER_TYPE, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)
-      || !warn_stringop_overflow)
-    return NULL_RTX;
-
-  /* The source and destination of the call.  */
-  tree dest = CALL_EXPR_ARG (exp, 0);
-  tree src = CALL_EXPR_ARG (exp, 1);
-
-  /* The exact number of bytes to write (not the maximum).  */
-  tree len = CALL_EXPR_ARG (exp, 2);
-  access_data data (exp, access_read_write);
-  /* The size of the destination object.  */
-  tree destsize = compute_objsize (dest, warn_stringop_overflow - 1, &data.dst);
-  check_access (exp, len, /*maxread=*/len, src, destsize, data.mode, &data);
-  return NULL_RTX;
-}
-
 /* Callback routine for store_by_pieces.  Read GET_MODE_BITSIZE (MODE)
    bytes from constant string DATA + OFFSET and return it as target
    constant.  */
@@ -3817,78 +3644,6 @@  check_strncat_sizes (tree exp, tree objsize)
 		       objsize, data.mode, &data);
 }
 
-/* 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)
-      || !warn_stringop_overflow)
-    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 maxread = CALL_EXPR_ARG (exp, 2);
-
-  /* Detect unterminated source (only).  */
-  if (!check_nul_terminated_array (exp, src, maxread))
-    return NULL_RTX;
-
-  /* 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.  Since the lengths are only used for warning and not
-     for code generation disable strict mode below.  */
-  tree maxlen = slen;
-  if (!maxlen)
-    {
-      c_strlen_data lendata = { };
-      get_range_strlen (src, &lendata, /* eltsize = */ 1);
-      maxlen = lendata.maxbound;
-    }
-
-  access_data data (exp, access_read_write);
-  /* Try to verify that the destination is big enough for the shortest
-     string.  First try to determine the size of the destination object
-     into which the source is being copied.  */
-  tree destsize = compute_objsize (dest, warn_stringop_overflow - 1, &data.dst);
-
-  /* Add one for the terminating nul.  */
-  tree srclen = (maxlen
-		 ? fold_build2 (PLUS_EXPR, size_type_node, maxlen,
-				size_one_node)
-		 : NULL_TREE);
-
-  /* The strncat function copies at most MAXREAD 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 (maxread) && tree_fits_uhwi_p (destsize)
-      && tree_int_cst_equal (destsize, maxread))
-    {
-      location_t loc = EXPR_LOCATION (exp);
-      warning_at (loc, OPT_Wstringop_overflow_,
-		  "%qD specified bound %E equals destination size",
-		  get_callee_fndecl (exp), maxread);
-
-      return NULL_RTX;
-    }
-
-  if (!srclen
-      || (maxread && tree_fits_uhwi_p (maxread)
-	  && tree_fits_uhwi_p (srclen)
-	  && tree_int_cst_lt (maxread, srclen)))
-    srclen = maxread;
-
-  check_access (exp, /*dstwrite=*/NULL_TREE, maxread, srclen,
-		destsize, data.mode, &data);
-  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.  */
 
@@ -3908,18 +3663,6 @@  expand_builtin_strncpy (tree exp, rtx target)
   /* The length of the source sequence.  */
   tree slen = c_strlen (src, 1);
 
-  if (warn_stringop_overflow)
-    {
-      access_data data (exp, access_read_write, len, true, len, true);
-      const int ost = warn_stringop_overflow ? warn_stringop_overflow - 1 : 1;
-      compute_objsize (src, ost, &data.src);
-      tree dstsize = compute_objsize (dest, ost, &data.dst);
-      /* The number of bytes to write is LEN but check_access will also
-	 check SLEN if LEN's value isn't known.  */
-      check_access (exp, /*dstwrite=*/len,
-		    /*maxread=*/len, src, dstsize, data.mode, &data);
-    }
-
   /* 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;
@@ -4141,8 +3884,6 @@  expand_builtin_memset (tree exp, rtx target, machine_mode mode)
   tree val = CALL_EXPR_ARG (exp, 1);
   tree len = CALL_EXPR_ARG (exp, 2);
 
-  check_memop_access (exp, dest, NULL_TREE, len);
-
   return expand_builtin_memset_args (dest, val, len, target, mode, exp);
 }
 
@@ -4470,8 +4211,6 @@  expand_builtin_bzero (tree exp)
   tree dest = CALL_EXPR_ARG (exp, 0);
   tree size = CALL_EXPR_ARG (exp, 1);
 
-  check_memop_access (exp, dest, NULL_TREE, size);
-
   /* 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
@@ -4622,10 +4361,6 @@  expand_builtin_strcmp (tree exp, ATTRIBUTE_UNUSED rtx target)
   tree arg1 = CALL_EXPR_ARG (exp, 0);
   tree arg2 = CALL_EXPR_ARG (exp, 1);
 
-  if (!check_read_access (exp, arg1)
-      || !check_read_access (exp, arg2))
-    return NULL_RTX;
-
   /* Due to the performance benefit, always inline the calls first.  */
   rtx result = NULL_RTX;
   result = inline_expand_builtin_bytecmp (exp, target);
@@ -4707,11 +4442,6 @@  expand_builtin_strcmp (tree exp, ATTRIBUTE_UNUSED rtx target)
   tree fndecl = get_callee_fndecl (exp);
   if (result)
     {
-      /* Check to see if the argument was declared attribute nonstring
-	 and if so, issue a warning since at this point it's not known
-	 to be nul-terminated.  */
-      maybe_warn_nonstring_arg (fndecl, exp);
-
       /* Return the value in the proper mode for this function.  */
       machine_mode mode = TYPE_MODE (TREE_TYPE (exp));
       if (GET_MODE (result) == mode)
@@ -4725,6 +4455,7 @@  expand_builtin_strcmp (tree exp, ATTRIBUTE_UNUSED rtx target)
   /* Expand the library call ourselves using a stabilized argument
      list to avoid re-evaluating the function's arguments twice.  */
   tree fn = build_call_nofold_loc (EXPR_LOCATION (exp), fndecl, 2, arg1, arg2);
+  copy_warning (fn, exp);
   gcc_assert (TREE_CODE (fn) == CALL_EXPR);
   CALL_EXPR_TAILCALL (fn) = CALL_EXPR_TAILCALL (exp);
   return expand_call (fn, target, target == const0_rtx);
@@ -4746,66 +4477,10 @@  expand_builtin_strncmp (tree exp, ATTRIBUTE_UNUSED rtx target,
   tree arg2 = CALL_EXPR_ARG (exp, 1);
   tree arg3 = CALL_EXPR_ARG (exp, 2);
 
-  if (!check_nul_terminated_array (exp, arg1, arg3)
-      || !check_nul_terminated_array (exp, arg2, arg3))
-    return NULL_RTX;
-
   location_t loc = EXPR_LOCATION (exp);
   tree len1 = c_strlen (arg1, 1);
   tree len2 = c_strlen (arg2, 1);
 
-  if (!len1 || !len2)
-    {
-      /* Check to see if the argument was declared attribute nonstring
-	 and if so, issue a warning since at this point it's not known
-	 to be nul-terminated.  */
-      if (!maybe_warn_nonstring_arg (get_callee_fndecl (exp), exp)
-	  && !len1 && !len2)
-	{
-	  /* A strncmp read is constrained not just by the bound but
-	     also by the length of the shorter string.  Specifying
-	     a bound that's larger than the size of either array makes
-	     no sense and is likely a bug.  When the length of neither
-	     of the two strings is known but the sizes of both of
-	     the arrays they are stored in is, issue a warning if
-	     the bound is larger than than the size of the larger
-	     of the two arrays.  */
-
-	  access_ref ref1 (arg3, true);
-	  access_ref ref2 (arg3, true);
-
-	  tree bndrng[2] = { NULL_TREE, NULL_TREE };
-	  get_size_range (arg3, bndrng, ref1.bndrng);
-
-	  tree size1 = compute_objsize (arg1, 1, &ref1);
-	  tree size2 = compute_objsize (arg2, 1, &ref2);
-	  tree func = get_callee_fndecl (exp);
-
-	  if (size1 && size2 && bndrng[0] && !integer_zerop (bndrng[0]))
-	    {
-	      offset_int rem1 = ref1.size_remaining ();
-	      offset_int rem2 = ref2.size_remaining ();
-	      if (rem1 == 0 || rem2 == 0)
-		maybe_warn_for_bound (OPT_Wstringop_overread, loc, exp, func,
-				      bndrng, integer_zero_node);
-	      else
-		{
-		  offset_int maxrem = wi::max (rem1, rem2, UNSIGNED);
-		  if (maxrem < wi::to_offset (bndrng[0]))
-		    maybe_warn_for_bound (OPT_Wstringop_overread, loc, exp,
-					  func, bndrng,
-					  wide_int_to_tree (sizetype, maxrem));
-		}
-	    }
-	  else if (bndrng[0]
-		   && !integer_zerop (bndrng[0])
-		   && ((size1 && integer_zerop (size1))
-		       || (size2 && integer_zerop (size2))))
-	    maybe_warn_for_bound (OPT_Wstringop_overread, loc, exp, func,
-				  bndrng, integer_zero_node);
-	}
-    }
-
   /* Due to the performance benefit, always inline the calls first.  */
   rtx result = NULL_RTX;
   result = inline_expand_builtin_bytecmp (exp, target);
@@ -7544,63 +7219,12 @@  expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode,
 	return target;
       break;
 
-    case BUILT_IN_STRCAT:
-      target = expand_builtin_strcat (exp);
-      if (target)
-	return target;
-      break;
-
-    case BUILT_IN_GETTEXT:
-    case BUILT_IN_PUTS:
-    case BUILT_IN_PUTS_UNLOCKED:
-    case BUILT_IN_STRDUP:
-      if (validate_arglist (exp, POINTER_TYPE, VOID_TYPE))
-	check_read_access (exp, CALL_EXPR_ARG (exp, 0));
-      break;
-
-    case BUILT_IN_INDEX:
-    case BUILT_IN_RINDEX:
-    case BUILT_IN_STRCHR:
-    case BUILT_IN_STRRCHR:
-      if (validate_arglist (exp, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE))
-	check_read_access (exp, CALL_EXPR_ARG (exp, 0));
-      break;
-
-    case BUILT_IN_FPUTS:
-    case BUILT_IN_FPUTS_UNLOCKED:
-      if (validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE))
-	check_read_access (exp, CALL_EXPR_ARG (exp, 0));
-      break;
-
-    case BUILT_IN_STRNDUP:
-      if (validate_arglist (exp, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE))
-	check_read_access (exp, CALL_EXPR_ARG (exp, 0), CALL_EXPR_ARG (exp, 1));
-      break;
-
-    case BUILT_IN_STRCASECMP:
-    case BUILT_IN_STRPBRK:
-    case BUILT_IN_STRSPN:
-    case BUILT_IN_STRCSPN:
-    case BUILT_IN_STRSTR:
-      if (validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE))
-	{
-	  check_read_access (exp, CALL_EXPR_ARG (exp, 0));
-	  check_read_access (exp, CALL_EXPR_ARG (exp, 1));
-	}
-      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)
@@ -7613,18 +7237,6 @@  expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode,
 	return target;
       break;
 
-    case BUILT_IN_STPNCPY:
-      target = expand_builtin_stpncpy (exp, target);
-      if (target)
-	return target;
-      break;
-
-    case BUILT_IN_MEMCHR:
-      target = expand_builtin_memchr (exp, target);
-      if (target)
-	return target;
-      break;
-
     case BUILT_IN_MEMCPY:
       target = expand_builtin_memcpy (exp, target);
       if (target)
@@ -8626,8 +8238,11 @@  fold_builtin_strlen (location_t loc, tree expr, tree type, tree arg)
       if (len)
 	return fold_convert_loc (loc, type, len);
 
+      /* TODO: Move this to gimple-ssa-warn-access once the pass runs
+	 also early enough to detect invalid reads in multimensional
+	 arrays and struct members.  */
       if (!lendata.decl)
-	c_strlen (arg, 1, &lendata);
+	 c_strlen (arg, 1, &lendata);
 
       if (lendata.decl)
 	{
diff --git a/gcc/builtins.h b/gcc/builtins.h
index 024ddbfa704..16b47ac1a7b 100644
--- a/gcc/builtins.h
+++ b/gcc/builtins.h
@@ -151,8 +151,4 @@  extern internal_fn replacement_internal_fn (gcall *);
 
 extern bool builtin_with_linkage_p (tree);
 
-class access_data;
-extern bool check_access (tree, tree, tree, tree, tree,
-			  access_mode, const access_data * = NULL);
-
 #endif /* GCC_BUILTINS_H */
diff --git a/gcc/calls.c b/gcc/calls.c
index 795d21414c1..fcb0d6dec69 100644
--- a/gcc/calls.c
+++ b/gcc/calls.c
@@ -61,7 +61,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "attr-fnspec.h"
 #include "value-query.h"
 #include "pointer-query.h"
-
+#include "gimple-ssa-warn-access.h"
 #include "tree-pretty-print.h"
 
 /* Like PREFERRED_STACK_BOUNDARY but in units of bytes, not bits.  */
@@ -1614,314 +1614,6 @@  get_attr_nonstring_decl (tree expr, tree *ref)
   return NULL_TREE;
 }
 
-/* Warn about passing a non-string array/pointer to a built-in function
-   that expects a nul-terminated string argument.  Returns true if
-   a warning has been issued.*/
-
-bool
-maybe_warn_nonstring_arg (tree fndecl, tree exp)
-{
-  if (!fndecl || !fndecl_built_in_p (fndecl, BUILT_IN_NORMAL))
-    return false;
-
-  if (!warn_stringop_overread || warning_suppressed_p (exp, OPT_Wstringop_overread))
-    return false;
-
-  /* Avoid clearly invalid calls (more checking done below).  */
-  unsigned nargs = call_expr_nargs (exp);
-  if (!nargs)
-    return false;
-
-  /* The bound argument to a bounded string function like strncpy.  */
-  tree bound = NULL_TREE;
-
-  /* The longest known or possible string argument to one of the comparison
-     functions.  If the length is less than the bound it is used instead.
-     Since the length is only used for warning and not for code generation
-     disable strict mode in the calls to get_range_strlen below.  */
-  tree maxlen = NULL_TREE;
-
-  /* It's safe to call "bounded" string functions with a non-string
-     argument since the functions provide an explicit bound for this
-     purpose.  The exception is strncat where the bound may refer to
-     either the destination or the source.  */
-  int fncode = DECL_FUNCTION_CODE (fndecl);
-  switch (fncode)
-    {
-    case BUILT_IN_STRCMP:
-    case BUILT_IN_STRNCMP:
-    case BUILT_IN_STRNCASECMP:
-      {
-	/* For these, if one argument refers to one or more of a set
-	   of string constants or arrays of known size, determine
-	   the range of their known or possible lengths and use it
-	   conservatively as the bound for the unbounded function,
-	   and to adjust the range of the bound of the bounded ones.  */
-	for (unsigned argno = 0;
-	     argno < MIN (nargs, 2)
-	       && !(maxlen && TREE_CODE (maxlen) == INTEGER_CST); argno++)
-	  {
-	    tree arg = CALL_EXPR_ARG (exp, argno);
-	    if (!get_attr_nonstring_decl (arg))
-	      {
-		c_strlen_data lendata = { };
-		/* Set MAXBOUND to an arbitrary non-null non-integer
-		   node as a request to have it set to the length of
-		   the longest string in a PHI.  */
-		lendata.maxbound = arg;
-		get_range_strlen (arg, &lendata, /* eltsize = */ 1);
-		maxlen = lendata.maxbound;
-	      }
-	  }
-      }
-      /* Fall through.  */
-
-    case BUILT_IN_STRNCAT:
-    case BUILT_IN_STPNCPY:
-    case BUILT_IN_STRNCPY:
-      if (nargs > 2)
-	bound = CALL_EXPR_ARG (exp, 2);
-      break;
-
-    case BUILT_IN_STRNDUP:
-      if (nargs > 1)
-	bound = CALL_EXPR_ARG (exp, 1);
-      break;
-
-    case BUILT_IN_STRNLEN:
-      {
-	tree arg = CALL_EXPR_ARG (exp, 0);
-	if (!get_attr_nonstring_decl (arg))
-	  {
-	    c_strlen_data lendata = { };
-	    /* Set MAXBOUND to an arbitrary non-null non-integer
-	       node as a request to have it set to the length of
-	       the longest string in a PHI.  */
-	    lendata.maxbound = arg;
-	    get_range_strlen (arg, &lendata, /* eltsize = */ 1);
-	    maxlen = lendata.maxbound;
-	  }
-	if (nargs > 1)
-	  bound = CALL_EXPR_ARG (exp, 1);
-	break;
-      }
-
-    default:
-      break;
-    }
-
-  /* Determine the range of the bound argument (if specified).  */
-  tree bndrng[2] = { NULL_TREE, NULL_TREE };
-  if (bound)
-    {
-      STRIP_NOPS (bound);
-      get_size_range (bound, bndrng);
-    }
-
-  location_t loc = EXPR_LOCATION (exp);
-
-  if (bndrng[0])
-    {
-      /* Diagnose excessive bound prior to the adjustment below and
-	 regardless of attribute nonstring.  */
-      tree maxobjsize = max_object_size ();
-      if (tree_int_cst_lt (maxobjsize, bndrng[0]))
-	{
-	  bool warned = false;
-	  if (tree_int_cst_equal (bndrng[0], bndrng[1]))
-	    warned = warning_at (loc, OPT_Wstringop_overread,
-				 "%qD specified bound %E "
-				 "exceeds maximum object size %E",
-				 fndecl, bndrng[0], maxobjsize);
-	  else
-	    warned = warning_at (loc, OPT_Wstringop_overread,
-				 "%qD specified bound [%E, %E] "
-				 "exceeds maximum object size %E",
-				 fndecl, bndrng[0], bndrng[1],
-				 maxobjsize);
-	  if (warned)
-	    suppress_warning (exp, OPT_Wstringop_overread);
-
-	  return warned;
-	}
-    }
-
-  if (maxlen && !integer_all_onesp (maxlen))
-    {
-      /* Add one for the nul.  */
-      maxlen = const_binop (PLUS_EXPR, TREE_TYPE (maxlen), maxlen,
-			    size_one_node);
-
-      if (!bndrng[0])
-	{
-	  /* Conservatively use the upper bound of the lengths for
-	     both the lower and the upper bound of the operation.  */
-	  bndrng[0] = maxlen;
-	  bndrng[1] = maxlen;
-	  bound = void_type_node;
-	}
-      else if (maxlen)
-	{
-	  /* Replace the bound on the operation with the upper bound
-	     of the length of the string if the latter is smaller.  */
-	  if (tree_int_cst_lt (maxlen, bndrng[0]))
-	    bndrng[0] = maxlen;
-	  else if (tree_int_cst_lt (maxlen, bndrng[1]))
-	    bndrng[1] = maxlen;
-	}
-    }
-
-  bool any_arg_warned = false;
-  /* Iterate over the built-in function's formal arguments and check
-     each const char* against the actual argument.  If the actual
-     argument is declared attribute non-string issue a warning unless
-     the argument's maximum length is bounded.  */
-  function_args_iterator it;
-  function_args_iter_init (&it, TREE_TYPE (fndecl));
-
-  for (unsigned argno = 0; ; ++argno, function_args_iter_next (&it))
-    {
-      /* Avoid iterating past the declared argument in a call
-	 to function declared without a prototype.  */
-      if (argno >= nargs)
-	break;
-
-      tree argtype = function_args_iter_cond (&it);
-      if (!argtype)
-	break;
-
-      if (TREE_CODE (argtype) != POINTER_TYPE)
-	continue;
-
-      argtype = TREE_TYPE (argtype);
-
-      if (TREE_CODE (argtype) != INTEGER_TYPE
-	  || !TYPE_READONLY (argtype))
-	continue;
-
-      argtype = TYPE_MAIN_VARIANT (argtype);
-      if (argtype != char_type_node)
-	continue;
-
-      tree callarg = CALL_EXPR_ARG (exp, argno);
-      if (TREE_CODE (callarg) == ADDR_EXPR)
-	callarg = TREE_OPERAND (callarg, 0);
-
-      /* See if the destination is declared with attribute "nonstring".  */
-      tree decl = get_attr_nonstring_decl (callarg);
-      if (!decl)
-	continue;
-
-      /* The maximum number of array elements accessed.  */
-      offset_int wibnd = 0;
-
-      if (argno && fncode == BUILT_IN_STRNCAT)
-	{
-	  /* See if the bound in strncat is derived from the length
-	     of the strlen of the destination (as it's expected to be).
-	     If so, reset BOUND and FNCODE to trigger a warning.  */
-	  tree dstarg = CALL_EXPR_ARG (exp, 0);
-	  if (is_strlen_related_p (dstarg, bound))
-	    {
-	      /* The bound applies to the destination, not to the source,
-		 so reset these to trigger a warning without mentioning
-		 the bound.  */
-	      bound = NULL;
-	      fncode = 0;
-	    }
-	  else if (bndrng[1])
-	    /* Use the upper bound of the range for strncat.  */
-	    wibnd = wi::to_offset (bndrng[1]);
-	}
-      else if (bndrng[0])
-	/* Use the lower bound of the range for functions other than
-	   strncat.  */
-	wibnd = wi::to_offset (bndrng[0]);
-
-      /* Determine the size of the argument array if it is one.  */
-      offset_int asize = wibnd;
-      bool known_size = false;
-      tree type = TREE_TYPE (decl);
-
-      /* Determine the array size.  For arrays of unknown bound and
-	 pointers reset BOUND to trigger the appropriate warning.  */
-      if (TREE_CODE (type) == ARRAY_TYPE)
-	{
-	  if (tree arrbnd = TYPE_DOMAIN (type))
-	    {
-	      if ((arrbnd = TYPE_MAX_VALUE (arrbnd)))
-		{
-		  asize = wi::to_offset (arrbnd) + 1;
-		  known_size = true;
-		}
-	    }
-	  else if (bound == void_type_node)
-	    bound = NULL_TREE;
-	}
-      else if (bound == void_type_node)
-	bound = NULL_TREE;
-
-      /* In a call to strncat with a bound in a range whose lower but
-	 not upper bound is less than the array size, reset ASIZE to
-	 be the same as the bound and the other variable to trigger
-	 the apprpriate warning below.  */
-      if (fncode == BUILT_IN_STRNCAT
-	  && bndrng[0] != bndrng[1]
-	  && wi::ltu_p (wi::to_offset (bndrng[0]), asize)
-	  && (!known_size
-	      || wi::ltu_p (asize, wibnd)))
-	{
-	  asize = wibnd;
-	  bound = NULL_TREE;
-	  fncode = 0;
-	}
-
-      bool warned = false;
-
-      auto_diagnostic_group d;
-      if (wi::ltu_p (asize, wibnd))
-	{
-	  if (bndrng[0] == bndrng[1])
-	    warned = warning_at (loc, OPT_Wstringop_overread,
-				 "%qD argument %i declared attribute "
-				 "%<nonstring%> is smaller than the specified "
-				 "bound %wu",
-				 fndecl, argno + 1, wibnd.to_uhwi ());
-	  else if (wi::ltu_p (asize, wi::to_offset (bndrng[0])))
-	    warned = warning_at (loc, OPT_Wstringop_overread,
-				 "%qD argument %i declared attribute "
-				 "%<nonstring%> is smaller than "
-				 "the specified bound [%E, %E]",
-				 fndecl, argno + 1, bndrng[0], bndrng[1]);
-	  else
-	    warned = warning_at (loc, OPT_Wstringop_overread,
-				 "%qD argument %i declared attribute "
-				 "%<nonstring%> may be smaller than "
-				 "the specified bound [%E, %E]",
-				 fndecl, argno + 1, bndrng[0], bndrng[1]);
-	}
-      else if (fncode == BUILT_IN_STRNCAT)
-	; /* Avoid warning for calls to strncat() when the bound
-	     is equal to the size of the non-string argument.  */
-      else if (!bound)
-	warned = warning_at (loc, OPT_Wstringop_overread,
-			     "%qD argument %i declared attribute %<nonstring%>",
-			     fndecl, argno + 1);
-
-      if (warned)
-	{
-	  inform (DECL_SOURCE_LOCATION (decl),
-		  "argument %qD declared here", decl);
-	  any_arg_warned = true;
-	}
-    }
-
-  if (any_arg_warned)
-    suppress_warning (exp, OPT_Wstringop_overread);
-
-  return any_arg_warned;
-}
-
 /* Issue an error if CALL_EXPR was flagged as requiring
    tall-call optimization.  */
 
diff --git a/gcc/diagnostic-spec.c b/gcc/diagnostic-spec.c
index fbe52c6445c..5e961e1b9f9 100644
--- a/gcc/diagnostic-spec.c
+++ b/gcc/diagnostic-spec.c
@@ -84,6 +84,7 @@  nowarn_spec_t::nowarn_spec_t (opt_code opt)
     case OPT_Wformat_overflow_:
     case OPT_Wformat_truncation_:
     case OPT_Wrestrict:
+    case OPT_Wsizeof_pointer_memaccess:
     case OPT_Wstrict_aliasing_:
     case OPT_Wstringop_overflow_:
     case OPT_Wstringop_overread:
diff --git a/gcc/gimple-fold.c b/gcc/gimple-fold.c
index ad7b140173f..3f2c176cff6 100644
--- a/gcc/gimple-fold.c
+++ b/gcc/gimple-fold.c
@@ -2073,7 +2073,7 @@  gimple_fold_builtin_strcpy (gimple_stmt_iterator *gsi,
     {
       /* Avoid folding calls with unterminated arrays.  */
       if (!warning_suppressed_p (stmt, OPT_Wstringop_overread))
-	warn_string_no_nul (loc, NULL_TREE, "strcpy", src, nonstr);
+	warn_string_no_nul (loc, stmt, "strcpy", src, nonstr);
       suppress_warning (stmt, OPT_Wstringop_overread);
       return false;
     }
@@ -3291,7 +3291,7 @@  gimple_fold_builtin_stpcpy (gimple_stmt_iterator *gsi)
     {
       /* Avoid folding calls with unterminated arrays.  */
       if (!warning_suppressed_p (stmt, OPT_Wstringop_overread))
-	warn_string_no_nul (loc, NULL_TREE, "stpcpy", src, data.decl, size,
+	warn_string_no_nul (loc, stmt, "stpcpy", src, data.decl, size,
 			    exact);
       suppress_warning (stmt, OPT_Wstringop_overread);
       return false;
diff --git a/gcc/gimple-ssa-warn-access.cc b/gcc/gimple-ssa-warn-access.cc
index e4d98b2ec28..93f43b711e2 100644
--- a/gcc/gimple-ssa-warn-access.cc
+++ b/gcc/gimple-ssa-warn-access.cc
@@ -40,6 +40,7 @@ 
 #include "tree-ssa.h"
 #include "tree-cfg.h"
 #include "tree-object-size.h"
+#include "tree-ssa-strlen.h"
 #include "calls.h"
 #include "cfgloop.h"
 #include "intl.h"
@@ -49,6 +50,83 @@ 
 #include "demangle.h"
 #include "pointer-query.h"
 
+/* Return true if STMT has an associated location.  */
+
+static inline location_t
+has_location (const gimple *stmt)
+{
+  return gimple_has_location (stmt);
+}
+
+/* Return true if tree node X has an associated location.  */
+
+static inline location_t
+has_location (const_tree x)
+{
+  if (DECL_P (x))
+    return DECL_SOURCE_LOCATION (x) != UNKNOWN_LOCATION;
+
+  if (EXPR_P (x))
+    return EXPR_HAS_LOCATION (x);
+
+  return false;
+}
+
+/* Return the associated location of STMT.  */
+
+static inline location_t
+get_location (const gimple *stmt)
+{
+  return gimple_location (stmt);
+}
+
+/* Return the associated location of tree node X.  */
+
+static inline location_t
+get_location (tree x)
+{
+  if (DECL_P (x))
+    return DECL_SOURCE_LOCATION (x);
+
+  if (EXPR_P (x))
+    return EXPR_LOCATION (x);
+
+  return UNKNOWN_LOCATION;
+}
+
+/* Overload of the nascent tree function for GIMPLE STMT.  */
+
+static inline tree
+get_callee_fndecl (const gimple *stmt)
+{
+  return gimple_call_fndecl (stmt);
+}
+
+static inline unsigned
+call_nargs (const gimple *stmt)
+{
+  return gimple_call_num_args (stmt);
+}
+
+static inline unsigned
+call_nargs (const_tree expr)
+{
+  return call_expr_nargs (expr);
+}
+
+
+static inline tree
+call_arg (const gimple *stmt, unsigned argno)
+{
+  return gimple_call_arg (stmt, argno);
+}
+
+static inline tree
+call_arg (tree expr, unsigned argno)
+{
+  return CALL_EXPR_ARG (expr, argno);
+}
+
 /* For a call EXPR at LOC to a function FNAME that expects a string
    in the argument ARG, issue a diagnostic due to it being a called
    with an argument that is a character array with no terminating
@@ -56,10 +134,10 @@ 
    of characters in which the NUL is expected.  Either EXPR or FNAME
    may be null but noth both.  SIZE may be null when BNDRNG is null.  */
 
-void
-warn_string_no_nul (location_t loc, tree expr, const char *fname,
-		    tree arg, tree decl, tree size /* = NULL_TREE */,
-		    bool exact /* = false */,
+template <class GimpleOrTree>
+static void
+warn_string_no_nul (location_t loc, GimpleOrTree expr, const char *fname,
+		    tree arg, tree decl, tree size, bool exact,
 		    const wide_int bndrng[2] /* = NULL */)
 {
   const opt_code opt = OPT_Wstringop_overread;
@@ -152,7 +230,7 @@  warn_string_no_nul (location_t loc, tree expr, const char *fname,
 
   if (warned)
     {
-      inform (DECL_SOURCE_LOCATION (decl),
+      inform (get_location (decl),
 	      "referenced argument declared here");
       suppress_warning (arg, opt);
       if (expr)
@@ -160,16 +238,80 @@  warn_string_no_nul (location_t loc, tree expr, const char *fname,
     }
 }
 
+void
+warn_string_no_nul (location_t loc, gimple *stmt, const char *fname,
+		    tree arg, tree decl, tree size /* = NULL_TREE */,
+		    bool exact /* = false */,
+		    const wide_int bndrng[2] /* = NULL */)
+{
+  return warn_string_no_nul<gimple *> (loc, stmt, fname,
+				       arg, decl, size, exact, bndrng);
+}
+
+void
+warn_string_no_nul (location_t loc, tree expr, const char *fname,
+		    tree arg, tree decl, tree size /* = NULL_TREE */,
+		    bool exact /* = false */,
+		    const wide_int bndrng[2] /* = NULL */)
+{
+  return warn_string_no_nul<tree> (loc, expr, fname,
+				   arg, decl, size, exact, bndrng);
+}
+
+/* If EXP refers to an unterminated constant character array return
+   the declaration of the object of which the array is a member or
+   element and if SIZE is not null, set *SIZE to the size of
+   the unterminated array and set *EXACT if the size is exact or
+   clear it otherwise.  Otherwise return null.  */
+
+tree
+unterminated_array (tree exp, tree *size /* = NULL */, bool *exact /* = NULL */)
+{
+  /* C_STRLEN will return NULL and set DECL in the info
+     structure if EXP references a unterminated array.  */
+  c_strlen_data lendata = { };
+  tree len = c_strlen (exp, 1, &lendata);
+  if (len || !lendata.minlen || !lendata.decl)
+    return NULL_TREE;
+
+  if (!size)
+    return lendata.decl;
+
+  len = lendata.minlen;
+  if (lendata.off)
+    {
+      /* Constant offsets are already accounted for in LENDATA.MINLEN,
+	 but not in a SSA_NAME + CST expression.  */
+      if (TREE_CODE (lendata.off) == INTEGER_CST)
+	*exact = true;
+      else if (TREE_CODE (lendata.off) == PLUS_EXPR
+	       && TREE_CODE (TREE_OPERAND (lendata.off, 1)) == INTEGER_CST)
+	{
+	  /* Subtract the offset from the size of the array.  */
+	  *exact = false;
+	  tree temp = TREE_OPERAND (lendata.off, 1);
+	  temp = fold_convert (ssizetype, temp);
+	  len = fold_build2 (MINUS_EXPR, ssizetype, len, temp);
+	}
+      else
+	*exact = false;
+    }
+  else
+    *exact = true;
+
+  *size = len;
+  return lendata.decl;
+}
+
 /* For a call EXPR (which may be null) that expects a string argument
    SRC as an argument, returns false if SRC is a character array with
    no terminating NUL.  When nonnull, BOUND is the number of characters
-   in which to expect the terminating NUL.  RDONLY is true for read-only
-   accesses such as strcmp, false for read-write such as strcpy.  When
-   EXPR is also issues a warning.  */
+   in which to expect the terminating NUL.  When EXPR is nonnull also
+   issues a warning.  */
 
-bool
-check_nul_terminated_array (tree expr, tree src,
-			    tree bound /* = NULL_TREE */)
+template <class GimpleOrTree>
+static bool
+check_nul_terminated_array (GimpleOrTree expr, tree src, tree bound)
 {
   /* The constant size of the array SRC points to.  The actual size
      may be less of EXACT is true, but not more.  */
@@ -208,66 +350,355 @@  check_nul_terminated_array (tree expr, tree src,
     }
 
   if (expr)
-    warn_string_no_nul (EXPR_LOCATION (expr), expr, NULL, src, nonstr,
+    warn_string_no_nul (get_location (expr), expr, NULL, src, nonstr,
 			size, exact, bound ? bndrng : NULL);
 
   return false;
 }
 
-/* If EXP refers to an unterminated constant character array return
-   the declaration of the object of which the array is a member or
-   element and if SIZE is not null, set *SIZE to the size of
-   the unterminated array and set *EXACT if the size is exact or
-   clear it otherwise.  Otherwise return null.  */
+bool
+check_nul_terminated_array (gimple *stmt, tree src, tree bound /* = NULL_TREE */)
+{
+  return check_nul_terminated_array<gimple *>(stmt, src, bound);
+}
 
-tree
-unterminated_array (tree exp, tree *size /* = NULL */, bool *exact /* = NULL */)
+bool
+check_nul_terminated_array (tree expr, tree src, tree bound /* = NULL_TREE */)
 {
-  /* C_STRLEN will return NULL and set DECL in the info
-     structure if EXP references a unterminated array.  */
-  c_strlen_data lendata = { };
-  tree len = c_strlen (exp, 1, &lendata);
-  if (len == NULL_TREE && lendata.minlen && lendata.decl)
-     {
-       if (size)
+  return check_nul_terminated_array<tree>(expr, src, bound);
+}
+
+/* Warn about passing a non-string array/pointer to a built-in function
+   that expects a nul-terminated string argument.  Returns true if
+   a warning has been issued.*/
+
+template <class GimpleOrTree>
+static bool
+maybe_warn_nonstring_arg (tree fndecl, GimpleOrTree exp)
+{
+  if (!fndecl || !fndecl_built_in_p (fndecl, BUILT_IN_NORMAL))
+    return false;
+
+  if (!warn_stringop_overread
+      || warning_suppressed_p (exp, OPT_Wstringop_overread))
+    return false;
+
+  /* Avoid clearly invalid calls (more checking done below).  */
+  unsigned nargs = call_nargs (exp);
+  if (!nargs)
+    return false;
+
+  /* The bound argument to a bounded string function like strncpy.  */
+  tree bound = NULL_TREE;
+
+  /* The longest known or possible string argument to one of the comparison
+     functions.  If the length is less than the bound it is used instead.
+     Since the length is only used for warning and not for code generation
+     disable strict mode in the calls to get_range_strlen below.  */
+  tree maxlen = NULL_TREE;
+
+  /* It's safe to call "bounded" string functions with a non-string
+     argument since the functions provide an explicit bound for this
+     purpose.  The exception is strncat where the bound may refer to
+     either the destination or the source.  */
+  int fncode = DECL_FUNCTION_CODE (fndecl);
+  switch (fncode)
+    {
+    case BUILT_IN_STRCMP:
+    case BUILT_IN_STRNCMP:
+    case BUILT_IN_STRNCASECMP:
+      {
+	/* For these, if one argument refers to one or more of a set
+	   of string constants or arrays of known size, determine
+	   the range of their known or possible lengths and use it
+	   conservatively as the bound for the unbounded function,
+	   and to adjust the range of the bound of the bounded ones.  */
+	for (unsigned argno = 0;
+	     argno < MIN (nargs, 2)
+	       && !(maxlen && TREE_CODE (maxlen) == INTEGER_CST); argno++)
+	  {
+	    tree arg = call_arg (exp, argno);
+	    if (!get_attr_nonstring_decl (arg))
+	      {
+		c_strlen_data lendata = { };
+		/* Set MAXBOUND to an arbitrary non-null non-integer
+		   node as a request to have it set to the length of
+		   the longest string in a PHI.  */
+		lendata.maxbound = arg;
+		get_range_strlen (arg, &lendata, /* eltsize = */ 1);
+		maxlen = lendata.maxbound;
+	      }
+	  }
+      }
+      /* Fall through.  */
+
+    case BUILT_IN_STRNCAT:
+    case BUILT_IN_STPNCPY:
+    case BUILT_IN_STRNCPY:
+      if (nargs > 2)
+	bound = call_arg (exp, 2);
+      break;
+
+    case BUILT_IN_STRNDUP:
+      if (nargs < 2)
+	return false;
+      bound = call_arg (exp, 1);
+      break;
+
+    case BUILT_IN_STRNLEN:
+      {
+	tree arg = call_arg (exp, 0);
+	if (!get_attr_nonstring_decl (arg))
+	  {
+	    c_strlen_data lendata = { };
+	    /* Set MAXBOUND to an arbitrary non-null non-integer
+	       node as a request to have it set to the length of
+	       the longest string in a PHI.  */
+	    lendata.maxbound = arg;
+	    get_range_strlen (arg, &lendata, /* eltsize = */ 1);
+	    maxlen = lendata.maxbound;
+	  }
+	if (nargs > 1)
+	  bound = call_arg (exp, 1);
+	break;
+      }
+
+    default:
+      break;
+    }
+
+  /* Determine the range of the bound argument (if specified).  */
+  tree bndrng[2] = { NULL_TREE, NULL_TREE };
+  if (bound)
+    {
+      STRIP_NOPS (bound);
+      get_size_range (bound, bndrng);
+    }
+
+  location_t loc = get_location (exp);
+
+  if (bndrng[0])
+    {
+      /* Diagnose excessive bound prior to the adjustment below and
+	 regardless of attribute nonstring.  */
+      tree maxobjsize = max_object_size ();
+      if (tree_int_cst_lt (maxobjsize, bndrng[0]))
+	{
+	  bool warned = false;
+	  if (tree_int_cst_equal (bndrng[0], bndrng[1]))
+	    warned = warning_at (loc, OPT_Wstringop_overread,
+				 "%qD specified bound %E "
+				 "exceeds maximum object size %E",
+				 fndecl, bndrng[0], maxobjsize);
+	  else
+	    warned = warning_at (loc, OPT_Wstringop_overread,
+				 "%qD specified bound [%E, %E] "
+				 "exceeds maximum object size %E",
+				 fndecl, bndrng[0], bndrng[1],
+				 maxobjsize);
+	  if (warned)
+	    suppress_warning (exp, OPT_Wstringop_overread);
+
+	  return warned;
+	}
+    }
+
+  if (maxlen && !integer_all_onesp (maxlen))
+    {
+      /* Add one for the nul.  */
+      maxlen = const_binop (PLUS_EXPR, TREE_TYPE (maxlen), maxlen,
+			    size_one_node);
+
+      if (!bndrng[0])
+	{
+	  /* Conservatively use the upper bound of the lengths for
+	     both the lower and the upper bound of the operation.  */
+	  bndrng[0] = maxlen;
+	  bndrng[1] = maxlen;
+	  bound = void_type_node;
+	}
+      else if (maxlen)
+	{
+	  /* Replace the bound on the operation with the upper bound
+	     of the length of the string if the latter is smaller.  */
+	  if (tree_int_cst_lt (maxlen, bndrng[0]))
+	    bndrng[0] = maxlen;
+	  else if (tree_int_cst_lt (maxlen, bndrng[1]))
+	    bndrng[1] = maxlen;
+	}
+    }
+
+  bool any_arg_warned = false;
+  /* Iterate over the built-in function's formal arguments and check
+     each const char* against the actual argument.  If the actual
+     argument is declared attribute non-string issue a warning unless
+     the argument's maximum length is bounded.  */
+  function_args_iterator it;
+  function_args_iter_init (&it, TREE_TYPE (fndecl));
+
+  for (unsigned argno = 0; ; ++argno, function_args_iter_next (&it))
+    {
+      /* Avoid iterating past the declared argument in a call
+	 to function declared without a prototype.  */
+      if (argno >= nargs)
+	break;
+
+      tree argtype = function_args_iter_cond (&it);
+      if (!argtype)
+	break;
+
+      if (TREE_CODE (argtype) != POINTER_TYPE)
+	continue;
+
+      argtype = TREE_TYPE (argtype);
+
+      if (TREE_CODE (argtype) != INTEGER_TYPE
+	  || !TYPE_READONLY (argtype))
+	continue;
+
+      argtype = TYPE_MAIN_VARIANT (argtype);
+      if (argtype != char_type_node)
+	continue;
+
+      tree callarg = call_arg (exp, argno);
+      if (TREE_CODE (callarg) == ADDR_EXPR)
+	callarg = TREE_OPERAND (callarg, 0);
+
+      /* See if the destination is declared with attribute "nonstring".  */
+      tree decl = get_attr_nonstring_decl (callarg);
+      if (!decl)
+	continue;
+
+      /* The maximum number of array elements accessed.  */
+      offset_int wibnd = 0;
+
+      if (argno && fncode == BUILT_IN_STRNCAT)
+	{
+	  /* See if the bound in strncat is derived from the length
+	     of the strlen of the destination (as it's expected to be).
+	     If so, reset BOUND and FNCODE to trigger a warning.  */
+	  tree dstarg = call_arg (exp, 0);
+	  if (is_strlen_related_p (dstarg, bound))
+	    {
+	      /* The bound applies to the destination, not to the source,
+		 so reset these to trigger a warning without mentioning
+		 the bound.  */
+	      bound = NULL;
+	      fncode = 0;
+	    }
+	  else if (bndrng[1])
+	    /* Use the upper bound of the range for strncat.  */
+	    wibnd = wi::to_offset (bndrng[1]);
+	}
+      else if (bndrng[0])
+	/* Use the lower bound of the range for functions other than
+	   strncat.  */
+	wibnd = wi::to_offset (bndrng[0]);
+
+      /* Determine the size of the argument array if it is one.  */
+      offset_int asize = wibnd;
+      bool known_size = false;
+      tree type = TREE_TYPE (decl);
+
+      /* Determine the array size.  For arrays of unknown bound and
+	 pointers reset BOUND to trigger the appropriate warning.  */
+      if (TREE_CODE (type) == ARRAY_TYPE)
 	{
-	  len = lendata.minlen;
-	  if (lendata.off)
+	  if (tree arrbnd = TYPE_DOMAIN (type))
 	    {
-	      /* Constant offsets are already accounted for in LENDATA.MINLEN,
-		 but not in a SSA_NAME + CST expression.  */
-	      if (TREE_CODE (lendata.off) == INTEGER_CST)
-		*exact = true;
-	      else if (TREE_CODE (lendata.off) == PLUS_EXPR
-		       && TREE_CODE (TREE_OPERAND (lendata.off, 1)) == INTEGER_CST)
+	      if ((arrbnd = TYPE_MAX_VALUE (arrbnd)))
 		{
-		  /* Subtract the offset from the size of the array.  */
-		  *exact = false;
-		  tree temp = TREE_OPERAND (lendata.off, 1);
-		  temp = fold_convert (ssizetype, temp);
-		  len = fold_build2 (MINUS_EXPR, ssizetype, len, temp);
+		  asize = wi::to_offset (arrbnd) + 1;
+		  known_size = true;
 		}
-	      else
-		*exact = false;
 	    }
+	  else if (bound == void_type_node)
+	    bound = NULL_TREE;
+	}
+      else if (bound == void_type_node)
+	bound = NULL_TREE;
+
+      /* In a call to strncat with a bound in a range whose lower but
+	 not upper bound is less than the array size, reset ASIZE to
+	 be the same as the bound and the other variable to trigger
+	 the apprpriate warning below.  */
+      if (fncode == BUILT_IN_STRNCAT
+	  && bndrng[0] != bndrng[1]
+	  && wi::ltu_p (wi::to_offset (bndrng[0]), asize)
+	  && (!known_size
+	      || wi::ltu_p (asize, wibnd)))
+	{
+	  asize = wibnd;
+	  bound = NULL_TREE;
+	  fncode = 0;
+	}
+
+      bool warned = false;
+
+      auto_diagnostic_group d;
+      if (wi::ltu_p (asize, wibnd))
+	{
+	  if (bndrng[0] == bndrng[1])
+	    warned = warning_at (loc, OPT_Wstringop_overread,
+				 "%qD argument %i declared attribute "
+				 "%<nonstring%> is smaller than the specified "
+				 "bound %wu",
+				 fndecl, argno + 1, wibnd.to_uhwi ());
+	  else if (wi::ltu_p (asize, wi::to_offset (bndrng[0])))
+	    warned = warning_at (loc, OPT_Wstringop_overread,
+				 "%qD argument %i declared attribute "
+				 "%<nonstring%> is smaller than "
+				 "the specified bound [%E, %E]",
+				 fndecl, argno + 1, bndrng[0], bndrng[1]);
 	  else
-	    *exact = true;
+	    warned = warning_at (loc, OPT_Wstringop_overread,
+				 "%qD argument %i declared attribute "
+				 "%<nonstring%> may be smaller than "
+				 "the specified bound [%E, %E]",
+				 fndecl, argno + 1, bndrng[0], bndrng[1]);
+	}
+      else if (fncode == BUILT_IN_STRNCAT)
+	; /* Avoid warning for calls to strncat() when the bound
+	     is equal to the size of the non-string argument.  */
+      else if (!bound)
+	warned = warning_at (loc, OPT_Wstringop_overread,
+			     "%qD argument %i declared attribute %<nonstring%>",
+			     fndecl, argno + 1);
 
-	  *size = len;
+      if (warned)
+	{
+	  inform (DECL_SOURCE_LOCATION (decl),
+		  "argument %qD declared here", decl);
+	  any_arg_warned = true;
 	}
-       return lendata.decl;
-     }
+    }
 
-  return NULL_TREE;
+  if (any_arg_warned)
+    suppress_warning (exp, OPT_Wstringop_overread);
+
+  return any_arg_warned;
+}
+
+bool
+maybe_warn_nonstring_arg (tree fndecl, gimple *stmt)
+{
+  return maybe_warn_nonstring_arg<gimple *>(fndecl, stmt);
+}
+
+
+bool
+maybe_warn_nonstring_arg (tree fndecl, tree expr)
+{
+  return maybe_warn_nonstring_arg<tree>(fndecl, expr);
 }
 
 /* Issue a warning OPT for a bounded call EXP with a bound in RANGE
    accessing an object with SIZE.  */
 
-bool
-maybe_warn_for_bound (opt_code opt, location_t loc, tree exp, tree func,
-		      tree bndrng[2], tree size,
-		      const access_data *pad /* = NULL */)
+template <class GimpleOrTree>
+static bool
+maybe_warn_for_bound (opt_code opt, location_t loc, GimpleOrTree exp, tree func,
+		      tree bndrng[2], tree size, const access_data *pad)
 {
   if (!bndrng[0] || warning_suppressed_p (exp, opt))
     return false;
@@ -352,15 +783,10 @@  maybe_warn_for_bound (opt_code opt, location_t loc, tree exp, tree func,
 				bndrng[0], bndrng[1], size));
       if (warned)
 	{
-	  if (pad && pad->src.ref)
-	    {
-	      if (DECL_P (pad->src.ref))
-		inform (DECL_SOURCE_LOCATION (pad->src.ref),
-			"source object declared here");
-	      else if (EXPR_HAS_LOCATION (pad->src.ref))
-		inform (EXPR_LOCATION (pad->src.ref),
-			"source object allocated here");
-	    }
+	  if (pad && pad->src.ref
+	      && has_location (pad->src.ref))
+	    inform (get_location (pad->src.ref),
+		    "source object allocated here");
 	  suppress_warning (exp, opt);
 	}
 
@@ -440,21 +866,33 @@  maybe_warn_for_bound (opt_code opt, location_t loc, tree exp, tree func,
 
   if (warned)
     {
-      if (pad && pad->dst.ref)
-	{
-	  if (DECL_P (pad->dst.ref))
-	    inform (DECL_SOURCE_LOCATION (pad->dst.ref),
-		    "destination object declared here");
-	  else if (EXPR_HAS_LOCATION (pad->dst.ref))
-	    inform (EXPR_LOCATION (pad->dst.ref),
-		    "destination object allocated here");
-	}
+      if (pad && pad->dst.ref
+	  && has_location (pad->dst.ref))
+	inform (get_location (pad->dst.ref),
+		"destination object allocated here");
       suppress_warning (exp, opt);
     }
 
   return warned;
 }
 
+bool
+maybe_warn_for_bound (opt_code opt, location_t loc, gimple *stmt, tree func,
+		      tree bndrng[2], tree size,
+		      const access_data *pad /* = NULL */)
+{
+  return maybe_warn_for_bound<gimple *> (opt, loc, stmt, func, bndrng, size,
+					 pad);
+}
+
+bool
+maybe_warn_for_bound (opt_code opt, location_t loc, tree expr, tree func,
+		      tree bndrng[2], tree size,
+		      const access_data *pad /* = NULL */)
+{
+  return maybe_warn_for_bound<tree> (opt, loc, expr, func, bndrng, size, pad);
+}
+
 /* For an expression EXP issue an access warning controlled by option OPT
    with access to a region SIZE bytes in size in the RANGE of sizes.
    WRITE is true for a write access, READ for a read access, neither for
@@ -462,9 +900,10 @@  maybe_warn_for_bound (opt_code opt, location_t loc, tree exp, tree func,
    is expected to valid.
    Returns true when a warning has been issued.  */
 
+template <class GimpleOrTree>
 static bool
-warn_for_access (location_t loc, tree func, tree exp, int opt, tree range[2],
-		 tree size, bool write, bool read, bool maybe)
+warn_for_access (location_t loc, tree func, GimpleOrTree exp, int opt,
+		 tree range[2], tree size, bool write, bool read, bool maybe)
 {
   bool warned = false;
 
@@ -719,6 +1158,22 @@  warn_for_access (location_t loc, tree func, tree exp, int opt, tree range[2],
   return warned;
 }
 
+static bool
+warn_for_access (location_t loc, tree func, gimple *stmt, int opt,
+		 tree range[2], tree size, bool write, bool read, bool maybe)
+{
+  return warn_for_access<gimple *>(loc, func, stmt, opt, range, size,
+				   write, read, maybe);
+}
+
+static bool
+warn_for_access (location_t loc, tree func, tree expr, int opt,
+		 tree range[2], tree size, bool write, bool read, bool maybe)
+{
+  return warn_for_access<tree>(loc, func, expr, opt, range, size,
+			       write, read, maybe);
+}
+
 /* Helper to set RANGE to the range of BOUND if it's nonnull, bounded
    by BNDRNG if nonnull and valid.  */
 
@@ -779,8 +1234,9 @@  get_size_range (tree bound, tree range[2], const offset_int bndrng[2])
    If the call is successfully verified as safe return true, otherwise
    return false.  */
 
-bool
-check_access (tree exp, tree dstwrite,
+template <class GimpleOrTree>
+static bool
+check_access (GimpleOrTree exp, tree dstwrite,
 	      tree maxread, tree srcstr, tree dstsize,
 	      access_mode mode, const access_data *pad /* = NULL */)
 {
@@ -809,7 +1265,10 @@  check_access (tree exp, tree dstwrite,
       if (POINTER_TYPE_P (TREE_TYPE (srcstr)))
 	{
 	  if (!check_nul_terminated_array (exp, srcstr, maxread))
+	    /* Return if the array is not nul-terminated and a warning
+	       has been issued.  */
 	    return false;
+
 	  /* Try to determine the range of lengths the source string
 	     refers to.  If it can be determined and is less than
 	     the upper bound given by MAXREAD add one to it for
@@ -881,7 +1340,7 @@  check_access (tree exp, tree dstwrite,
       && TREE_CODE (range[0]) == INTEGER_CST
       && tree_int_cst_lt (maxobjsize, range[0]))
     {
-      location_t loc = EXPR_LOCATION (exp);
+      location_t loc = get_location (exp);
       maybe_warn_for_bound (OPT_Wstringop_overflow_, loc, exp, func, range,
 			    NULL_TREE, pad);
       return false;
@@ -909,7 +1368,7 @@  check_access (tree exp, tree dstwrite,
 		  && warning_suppressed_p (pad->dst.ref, opt)))
 	    return false;
 
-	  location_t loc = EXPR_LOCATION (exp);
+	  location_t loc = get_location (exp);
 	  bool warned = false;
 	  if (dstwrite == slen && at_least_one)
 	    {
@@ -962,7 +1421,7 @@  check_access (tree exp, tree dstwrite,
 	 PAD is nonnull and BNDRNG is valid.  */
       get_size_range (maxread, range, pad ? pad->src.bndrng : NULL);
 
-      location_t loc = EXPR_LOCATION (exp);
+      location_t loc = get_location (exp);
       tree size = dstsize;
       if (pad && pad->mode == access_read_only)
 	size = wide_int_to_tree (sizetype, pad->src.sizrng[1]);
@@ -1023,7 +1482,7 @@  check_access (tree exp, tree dstwrite,
 	      && warning_suppressed_p (pad->src.ref, opt)))
 	return false;
 
-      location_t loc = EXPR_LOCATION (exp);
+      location_t loc = get_location (exp);
       const bool read
 	= mode == access_read_only || mode == access_read_write;
       const bool maybe = pad && pad->dst.parmarray;
@@ -1040,6 +1499,96 @@  check_access (tree exp, tree dstwrite,
   return true;
 }
 
+bool
+check_access (gimple *stmt, tree dstwrite,
+	      tree maxread, tree srcstr, tree dstsize,
+	      access_mode mode, const access_data *pad /* = NULL */)
+{
+  return check_access<gimple *>(stmt, dstwrite, maxread, srcstr, dstsize,
+				mode, pad);
+}
+
+bool
+check_access (tree expr, tree dstwrite,
+	      tree maxread, tree srcstr, tree dstsize,
+	      access_mode mode, const access_data *pad /* = NULL */)
+{
+  return check_access<tree>(expr, dstwrite, maxread, srcstr, dstsize,
+			    mode, pad);
+}
+
+/* Helper to determine and check the sizes of the source and the destination
+   of calls to __builtin_{bzero,memcpy,mempcpy,memset} calls.  EXP is the
+   call expression, DEST is the destination argument, SRC is the source
+   argument or null, and LEN is the number of bytes.  Use Object Size type-0
+   regardless of the OPT_Wstringop_overflow_ setting.  Return true on success
+   (no overflow or invalid sizes), false otherwise.  */
+
+template <class GimpleOrTree>
+static bool
+check_memop_access (GimpleOrTree expr, tree dest, tree src, tree size)
+{
+  /* For functions like memset and memcpy that operate on raw memory
+     try to determine the size of the largest source and destination
+     object using type-0 Object Size regardless of the object size
+     type specified by the option.  */
+  access_data data (expr, access_read_write);
+  tree srcsize = src ? compute_objsize (src, 0, &data.src) : NULL_TREE;
+  tree dstsize = compute_objsize (dest, 0, &data.dst);
+
+  return check_access (expr, size, /*maxread=*/NULL_TREE,
+		       srcsize, dstsize, data.mode, &data);
+}
+
+bool
+check_memop_access (gimple *stmt, tree dest, tree src, tree size)
+{
+  return check_memop_access<gimple *>(stmt, dest, src, size);
+}
+
+bool
+check_memop_access (tree expr, tree dest, tree src, tree size)
+{
+  return check_memop_access<tree>(expr, dest, src, size);
+}
+
+/* A convenience wrapper for check_access above to check access
+   by a read-only function like puts.  */
+
+template <class GimpleOrTree>
+static bool
+check_read_access (GimpleOrTree expr, tree src, tree bound, int ost)
+{
+  if (!warn_stringop_overread)
+    return true;
+
+  if (bound && !useless_type_conversion_p (size_type_node, TREE_TYPE (bound)))
+    bound = fold_convert (size_type_node, bound);
+
+  tree fndecl = get_callee_fndecl (expr);
+  maybe_warn_nonstring_arg (fndecl, expr);
+
+  access_data data (expr, access_read_only, NULL_TREE, false, bound, true);
+  compute_objsize (src, ost, &data.src);
+  return check_access (expr, /*dstwrite=*/ NULL_TREE, /*maxread=*/ bound,
+		       /*srcstr=*/ src, /*dstsize=*/ NULL_TREE, data.mode,
+		       &data);
+}
+
+bool
+check_read_access (gimple *stmt, tree src, tree bound /* = NULL_TREE */,
+		   int ost /* = 1 */)
+{
+  return check_read_access<gimple *>(stmt, src, bound, ost);
+}
+
+bool
+check_read_access (tree expr, tree src, tree bound /* = NULL_TREE */,
+		   int ost /* = 1 */)
+{
+  return check_read_access<tree>(expr, src, bound, ost);
+}
+
 /* Return true if STMT is a call to an allocation function.  Unless
    ALL_ALLOC is set, consider only functions that return dynmamically
    allocated objects.  Otherwise return true even for all forms of
@@ -1523,13 +2077,13 @@  warn_dealloc_offset (location_t loc, gimple *call, const access_ref &aref)
     return false;
 
   if (DECL_P (aref.ref))
-    inform (DECL_SOURCE_LOCATION (aref.ref), "declared here");
+    inform (get_location (aref.ref), "declared here");
   else if (TREE_CODE (aref.ref) == SSA_NAME)
     {
       gimple *def_stmt = SSA_NAME_DEF_STMT (aref.ref);
       if (is_gimple_call (def_stmt))
 	{
-	  location_t def_loc = gimple_location (def_stmt);
+	  location_t def_loc = get_location (def_stmt);
 	  tree alloc_decl = gimple_call_fndecl (def_stmt);
 	  if (alloc_decl)
 	    inform (def_loc,
@@ -1550,7 +2104,7 @@  warn_dealloc_offset (location_t loc, gimple *call, const access_ref &aref)
    a matching allocation function such as malloc or the corresponding
    form of C++ operatorn new.  */
 
-void
+static void
 maybe_emit_free_warning (gcall *call)
 {
   tree fndecl = gimple_call_fndecl (call);
@@ -1584,10 +2138,7 @@  maybe_emit_free_warning (gcall *call)
 			 "%qD called on unallocated object %qD",
 			 dealloc_decl, ref))
 	{
-	  loc = (DECL_P (ref)
-		 ? DECL_SOURCE_LOCATION (ref)
-		 : EXPR_LOCATION (ref));
-	  inform (loc, "declared here");
+	  inform (get_location (ref), "declared here");
 	  return;
 	}
 
@@ -1704,8 +2255,14 @@  class pass_waccess : public gimple_opt_pass
   virtual bool gate (function *);
   virtual unsigned int execute (function *);
 
+  /* Check a call to a built-in function.  */
+  bool check_builtin (gcall *);
+
+  /* Check statements in a basic block.  */
   void check (basic_block);
-  void check (gcall *);
+
+  /* Check a call to a function.  */
+ void check (gcall *);
 
 private:
   gimple_ranger *m_ranger;
@@ -1721,11 +2278,376 @@  pass_waccess::gate (function *)
 	  || warn_mismatched_new_delete);
 }
 
+/* Check a call STMT to strcat() for overflow and warn if it does.  */
+
+static void
+check_strcat (gimple *stmt)
+{
+  if (!warn_stringop_overflow)
+    return;
+
+  tree dest = call_arg (stmt, 0);
+  tree src = call_arg (stmt, 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 souce string is longer than
+     the destination object.  */
+  access_data data (stmt, access_read_write, NULL_TREE, true,
+		    NULL_TREE, true);
+  const int ost = warn_stringop_overflow ? warn_stringop_overflow - 1 : 1;
+  compute_objsize (src, ost, &data.src);
+  tree destsize = compute_objsize (dest, ost, &data.dst);
+
+  check_access (stmt, /*dstwrite=*/NULL_TREE, /*maxread=*/NULL_TREE,
+		src, destsize, data.mode, &data);
+}
+
+/* Check a call STMT to strcat() for overflow and warn if it does.  */
+
+static void
+check_strncat (gimple *stmt)
+{
+  if (!warn_stringop_overflow)
+    return;
+
+  tree dest = call_arg (stmt, 0);
+  tree src = call_arg (stmt, 1);
+  /* The upper bound on the number of bytes to write.  */
+  tree maxread = call_arg (stmt, 2);
+
+  /* Detect unterminated source (only).  */
+  if (!check_nul_terminated_array (stmt, src, maxread))
+    return;
+
+  /* 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.  Since the lengths are only used for warning and not
+     for code generation disable strict mode below.  */
+  tree maxlen = slen;
+  if (!maxlen)
+    {
+      c_strlen_data lendata = { };
+      get_range_strlen (src, &lendata, /* eltsize = */ 1);
+      maxlen = lendata.maxbound;
+    }
+
+  access_data data (stmt, access_read_write);
+  /* Try to verify that the destination is big enough for the shortest
+     string.  First try to determine the size of the destination object
+     into which the source is being copied.  */
+  tree destsize = compute_objsize (dest, warn_stringop_overflow - 1, &data.dst);
+
+  /* Add one for the terminating nul.  */
+  tree srclen = (maxlen
+		 ? fold_build2 (PLUS_EXPR, size_type_node, maxlen,
+				size_one_node)
+		 : NULL_TREE);
+
+  /* The strncat function copies at most MAXREAD 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 (maxread) && tree_fits_uhwi_p (destsize)
+      && tree_int_cst_equal (destsize, maxread))
+    {
+      location_t loc = get_location (stmt);
+      warning_at (loc, OPT_Wstringop_overflow_,
+		  "%qD specified bound %E equals destination size",
+		  get_callee_fndecl (stmt), maxread);
+
+      return;
+    }
+
+  if (!srclen
+      || (maxread && tree_fits_uhwi_p (maxread)
+	  && tree_fits_uhwi_p (srclen)
+	  && tree_int_cst_lt (maxread, srclen)))
+    srclen = maxread;
+
+  check_access (stmt, /*dstwrite=*/NULL_TREE, maxread, srclen,
+		destsize, data.mode, &data);
+}
+
+/* Check a call STMT to stpcpy() or strcpy() for overflow and warn
+   if it does.  */
+
+static void
+check_stxcpy (gimple *stmt)
+{
+  tree dst = call_arg (stmt, 0);
+  tree src = call_arg (stmt, 1);
+
+  tree size;
+  bool exact;
+  if (tree nonstr = unterminated_array (src, &size, &exact))
+    {
+      /* NONSTR refers to the non-nul terminated constant array.  */
+      warn_string_no_nul (get_location (stmt), stmt, NULL, src, nonstr,
+			  size, exact);
+      return;
+    }
+
+  if (warn_stringop_overflow)
+    {
+      access_data data (stmt, access_read_write, NULL_TREE, true,
+			NULL_TREE, true);
+      const int ost = warn_stringop_overflow ? warn_stringop_overflow - 1 : 1;
+      compute_objsize (src, ost, &data.src);
+      tree dstsize = compute_objsize (dst, ost, &data.dst);
+      check_access (stmt, /*dstwrite=*/ NULL_TREE,
+		    /*maxread=*/ NULL_TREE, /*srcstr=*/ src,
+		    dstsize, data.mode, &data);
+    }
+
+  /* Check to see if the argument was declared attribute nonstring
+     and if so, issue a warning since at this point it's not known
+     to be nul-terminated.  */
+  tree fndecl = get_callee_fndecl (stmt);
+  maybe_warn_nonstring_arg (fndecl, stmt);
+}
+
+/* Check a call STMT to stpncpy() or strncpy() for overflow and warn
+   if it does.  */
+
+static void
+check_stxncpy (gimple *stmt)
+{
+  if (!warn_stringop_overflow)
+    return;
+
+  tree dst = call_arg (stmt, 0);
+  tree src = call_arg (stmt, 1);
+  /* The number of bytes to write (not the maximum).  */
+  tree len = call_arg (stmt, 2);
+
+  access_data data (stmt, access_read_write, len, true, len, true);
+  const int ost = warn_stringop_overflow ? warn_stringop_overflow - 1 : 1;
+  compute_objsize (src, ost, &data.src);
+  tree dstsize = compute_objsize (dst, ost, &data.dst);
+
+  check_access (stmt, /*dstwrite=*/len,
+		/*maxread=*/len, src, dstsize, data.mode, &data);
+}
+
+/* Check a call STMT to stpncpy() or strncpy() for overflow and warn
+   if it does.  */
+
+static void
+check_strncmp (gimple *stmt)
+{
+  if (!warn_stringop_overread)
+    return;
+
+  tree arg1 = call_arg (stmt, 0);
+  tree arg2 = call_arg (stmt, 1);
+  tree bound = call_arg (stmt, 2);
+
+  /* First check each argument separately, considering the bound.  */
+  if (!check_nul_terminated_array (stmt, arg1, bound)
+      || !check_nul_terminated_array (stmt, arg2, bound))
+    return;
+
+  /* A strncmp read from each argument is constrained not just by
+     the bound but also by the length of the shorter string.  Specifying
+     a bound that's larger than the size of either array makes no sense
+     and is likely a bug.  When the length of neither of the two strings
+     is known but the sizes of both of the arrays they are stored in is,
+     issue a warning if the bound is larger than than the size of
+     the larger of the two arrays.  */
+
+  c_strlen_data lendata1{ }, lendata2{ };
+  tree len1 = c_strlen (arg1, 1, &lendata1);
+  tree len2 = c_strlen (arg2, 1, &lendata2);
+
+  if (len1 && len2)
+    /* If the length of both arguments was computed they must both be
+       nul-terminated and no further checking is necessary regardless
+       of the bound.  */
+    return;
+
+  /* Check to see if the argument was declared with attribute nonstring
+     and if so, issue a warning since at this point it's not known to be
+     nul-terminated.  */
+  if (maybe_warn_nonstring_arg (get_callee_fndecl (stmt), stmt))
+    return;
+
+  access_data adata1 (stmt, access_read_only, NULL_TREE, false, bound, true);
+  access_data adata2 (stmt, access_read_only, NULL_TREE, false, bound, true);
+
+  /* Determine the range of the bound first and bail if it fails; it's
+     cheaper than computing the size of the objects.  */
+  tree bndrng[2] = { NULL_TREE, NULL_TREE };
+  get_size_range (bound, bndrng, adata1.src.bndrng);
+  if (!bndrng[0] || integer_zerop (bndrng[0]))
+    return;
+
+  if (len1 && tree_int_cst_lt (len1, bndrng[0]))
+    bndrng[0] = len1;
+  if (len2 && tree_int_cst_lt (len2, bndrng[0]))
+    bndrng[0] = len2;
+
+  /* compute_objsize almost never fails (and ultimately should never
+     fail).  Don't bother to handle the rare case when it does.  */
+  if (!compute_objsize (arg1, 1, &adata1.src)
+      || !compute_objsize (arg2, 1, &adata2.src))
+    return;
+
+  /* Compute the size of the remaining space in each array after
+     subtracting any offset into it.  */
+  offset_int rem1 = adata1.src.size_remaining ();
+  offset_int rem2 = adata2.src.size_remaining ();
+
+  /* Cap REM1 and REM2 at the other if the other's argument is known
+     to be an unterminated array, either because there's no space
+     left in it after adding its offset or because it's constant and
+     has no nul.  */
+  if (rem1 == 0 || (rem1 < rem2 && lendata1.decl))
+    rem2 = rem1;
+  else if (rem2 == 0 || (rem2 < rem1 && lendata2.decl))
+    rem1 = rem2;
+
+  /* Point PAD at the array to reference in the note if a warning
+     is issued.  */
+  access_data *pad = len1 ? &adata2 : &adata1;
+  offset_int maxrem = wi::max (rem1, rem2, UNSIGNED);
+  if (lendata1.decl || lendata2.decl
+      || maxrem < wi::to_offset (bndrng[0]))
+    {
+      /* Warn when either argument isn't nul-terminated or the maximum
+	 remaining space in the two arrays is less than the bound.  */
+      tree func = get_callee_fndecl (stmt);
+      location_t loc = gimple_location (stmt);
+      maybe_warn_for_bound (OPT_Wstringop_overread, loc, stmt, func,
+			    bndrng, wide_int_to_tree (sizetype, maxrem),
+			    pad);
+    }
+}
+
+/* Check call STMT to a built-in function for invalid accesses.  Return
+   true if a call has been handled.  */
+
+bool
+pass_waccess::check_builtin (gcall *stmt)
+{
+  tree callee = gimple_call_fndecl (stmt);
+  if (!callee)
+    return false;
+
+  switch (DECL_FUNCTION_CODE (callee))
+    {
+    case BUILT_IN_GETTEXT:
+    case BUILT_IN_PUTS:
+    case BUILT_IN_PUTS_UNLOCKED:
+    case BUILT_IN_STRDUP:
+      check_read_access (stmt, call_arg (stmt, 0));
+      return true;
+
+    case BUILT_IN_INDEX:
+    case BUILT_IN_RINDEX:
+    case BUILT_IN_STRCHR:
+    case BUILT_IN_STRRCHR:
+    case BUILT_IN_STRLEN:
+      check_read_access (stmt, call_arg (stmt, 0));
+      return true;
+
+    case BUILT_IN_FPUTS:
+    case BUILT_IN_FPUTS_UNLOCKED:
+      check_read_access (stmt, call_arg (stmt, 0));
+      return true;
+
+    case BUILT_IN_STRNDUP:
+    case BUILT_IN_STRNLEN:
+      check_read_access (stmt, call_arg (stmt, 0), call_arg (stmt, 1));
+      return true;
+
+    case BUILT_IN_STRCAT:
+      check_strcat (stmt);
+      return true;
+
+    case BUILT_IN_STRNCAT:
+      check_strncat (stmt);
+      return true;
+
+    case BUILT_IN_STPCPY:
+    case BUILT_IN_STRCPY:
+      check_stxcpy (stmt);
+      return true;
+
+    case BUILT_IN_STPNCPY:
+    case BUILT_IN_STRNCPY:
+      check_stxncpy (stmt);
+      return true;
+
+    case BUILT_IN_STRCASECMP:
+    case BUILT_IN_STRCMP:
+    case BUILT_IN_STRPBRK:
+    case BUILT_IN_STRSPN:
+    case BUILT_IN_STRCSPN:
+    case BUILT_IN_STRSTR:
+      check_read_access (stmt, call_arg (stmt, 0));
+      check_read_access (stmt, call_arg (stmt, 1));
+      return true;
+
+    case BUILT_IN_STRNCASECMP:
+    case BUILT_IN_STRNCMP:
+      check_strncmp (stmt);
+      return true;
+
+    case BUILT_IN_MEMCMP:
+      {
+	tree a1 = call_arg (stmt, 0);
+	tree a2 = call_arg (stmt, 1);
+	tree len = call_arg (stmt, 2);
+	check_read_access (stmt, a1, len, 0);
+	check_read_access (stmt, a2, len, 0);
+	return true;
+      }
+
+    case BUILT_IN_MEMCPY:
+    case BUILT_IN_MEMPCPY:
+    case BUILT_IN_MEMMOVE:
+      {
+	tree dst = call_arg (stmt, 0);
+	tree src = call_arg (stmt, 1);
+	tree len = call_arg (stmt, 2);
+	check_memop_access (stmt, dst, src, len);
+	return true;
+      }
+
+    case BUILT_IN_MEMCHR:
+      {
+	tree src = call_arg (stmt, 0);
+	tree len = call_arg (stmt, 2);
+	check_read_access (stmt, src, len, 0);
+	return true;
+      }
+
+    case BUILT_IN_MEMSET:
+      {
+	tree dst = call_arg (stmt, 0);
+	tree len = call_arg (stmt, 2);
+	check_memop_access (stmt, dst, NULL_TREE, len);
+	return true;
+      }
+	
+    default:
+      return false;
+    }
+
+  return true;
+}
+
 /* Check call STMT for invalid accesses.  */
 
 void
 pass_waccess::check (gcall *stmt)
 {
+  if (gimple_call_builtin_p (stmt, BUILT_IN_NORMAL)
+      && check_builtin (stmt))
+    return;
+
   maybe_emit_free_warning (stmt);
 }
 
diff --git a/gcc/gimple-ssa-warn-access.h b/gcc/gimple-ssa-warn-access.h
index 6197574cf44..8b33ecbd4fb 100644
--- a/gcc/gimple-ssa-warn-access.h
+++ b/gcc/gimple-ssa-warn-access.h
@@ -24,6 +24,9 @@ 
 #define GCC_GIMPLE_SSA_WARN_ACCESS_H
 
 extern bool check_nul_terminated_array (tree, tree, tree = NULL_TREE);
+extern void warn_string_no_nul (location_t, gimple *, const char *, tree,
+				tree, tree = NULL_TREE, bool = false,
+				const wide_int[2] = NULL);
 extern void warn_string_no_nul (location_t, tree, const char *, tree,
 				tree, tree = NULL_TREE, bool = false,
 				const wide_int[2] = NULL);
@@ -31,7 +34,17 @@  extern tree unterminated_array (tree, tree * = NULL, bool * = NULL);
 extern void get_size_range (tree, tree[2], const offset_int[2]);
 
 class access_data;
+extern bool maybe_warn_for_bound (opt_code, location_t, gimple *, tree,
+				  tree[2], tree, const access_data * = NULL);
 extern bool maybe_warn_for_bound (opt_code, location_t, tree, tree,
 				  tree[2], tree, const access_data * = NULL);
 
+class access_data;
+extern bool check_access (tree, tree, tree, tree, tree, access_mode,
+			  const access_data * = NULL);
+
+extern bool check_memop_access (tree, tree, tree, tree);
+extern bool check_read_access (gimple *, tree, tree = NULL_TREE, int ost = 1);
+extern bool check_read_access (tree, tree, tree = NULL_TREE, int = 1);
+
 #endif   // GCC_GIMPLE_SSA_WARN_ACCESS_H
diff --git a/gcc/pointer-query.h b/gcc/pointer-query.h
index 6168c809ccc..8bd538a3ac2 100644
--- a/gcc/pointer-query.h
+++ b/gcc/pointer-query.h
@@ -203,14 +203,24 @@  public:
    functions like memcpy.  */
 struct access_data
 {
+  /* Set the access to at most MAXWRITE and MAXREAD bytes, and
+     at least 1 when MINWRITE or MINREAD, respectively, is set.  */
+  access_data (gimple *stmt, access_mode mode,
+	       tree maxwrite = NULL_TREE, bool minwrite = false,
+	       tree maxread = NULL_TREE, bool minread = false)
+    : stmt (stmt), call (),
+      dst (maxwrite, minwrite), src (maxread, minread), mode (mode) { }
+
   /* Set the access to at most MAXWRITE and MAXREAD bytes, and
      at least 1 when MINWRITE or MINREAD, respectively, is set.  */
   access_data (tree expr, access_mode mode,
 	       tree maxwrite = NULL_TREE, bool minwrite = false,
 	       tree maxread = NULL_TREE, bool minread = false)
-    : call (expr),
+    : stmt (), call (expr),
       dst (maxwrite, minwrite), src (maxread, minread), mode (mode) { }
 
+  /* Access statement.  */
+  gimple *stmt;
   /* Built-in function call.  */
   tree call;
   /* Destination and source of the access.  */
diff --git a/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess1.c b/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess1.c
index 38e06ba5e62..6c8866d2a24 100644
--- a/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess1.c
+++ b/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess1.c
@@ -1,7 +1,7 @@ 
 /* Test -Wsizeof-pointer-memaccess warnings.  */
 /* { dg-do compile } */
-/* { dg-options "-Wall -Wno-array-bounds -Wno-sizeof-array-argument -Wno-stringop-overflow" } */
-/* { dg-options "-Wall -Wno-array-bounds -Wno-sizeof-array-argument -Wno-c++-compat -Wno-stringop-overflow" { target c } } */
+/* { dg-options "-Wall -Wno-array-bounds -Wno-sizeof-array-argument -Wno-stringop-overflow -Wno-stringop-overread" } */
+/* { dg-options "-Wall -Wno-array-bounds -Wno-sizeof-array-argument -Wno-c++-compat -Wno-stringop-overflow -Wno-stringop-overread" { target c } } */
 /* { dg-require-effective-target alloca } */
 
 typedef __SIZE_TYPE__ size_t;
diff --git a/gcc/testsuite/c-c++-common/attr-nonstring-3.c b/gcc/testsuite/c-c++-common/attr-nonstring-3.c
index e3ceabd0836..3add7fae4fe 100644
--- a/gcc/testsuite/c-c++-common/attr-nonstring-3.c
+++ b/gcc/testsuite/c-c++-common/attr-nonstring-3.c
@@ -379,9 +379,9 @@  void test_stnrdup_warn (struct MemArrays *p)
   T (strndup (p->arr, N));
 
 
-  T (strndup (arr, N + 1));     /* { dg-warning "specified bound 5 exceeds source size 4" } */
+  T (strndup (arr, N + 1));     /* { dg-warning "argument 1 declared attribute 'nonstring' is smaller than the specified bound 5|specified bound 5 exceeds source size 4" } */
   T (strndup (parr, N + 1));
-  T (strndup (p->arr, N + 1));  /* { dg-warning "specified bound 5 exceeds source size 4" } */
+  T (strndup (p->arr, N + 1));  /* { dg-warning "argument 1 declared attribute 'nonstring' is smaller than the specified bound 5|specified bound 5 exceeds source size 4" } */
   T (strndup (p->parr, N + 1));
 }
 
diff --git a/gcc/testsuite/gcc.dg/Warray-bounds-39.c b/gcc/testsuite/gcc.dg/Warray-bounds-39.c
index 83176564bc3..cb00fa9c47d 100644
--- a/gcc/testsuite/gcc.dg/Warray-bounds-39.c
+++ b/gcc/testsuite/gcc.dg/Warray-bounds-39.c
@@ -5,6 +5,8 @@ 
    { dg-do compile }
    { dg-options "-O2 -Wall" }  */
 
+#define NOIPA __attribute__ ((noipa))
+
 typedef __SIZE_TYPE__ size_t;
 
 extern void* memcpy (void*, const void*, size_t);
@@ -19,65 +21,65 @@  const char s1_0[1][0] = { };
 
 char d[4];
 
-void* test_memcpy_s0_1 (void *d)
+NOIPA void* test_memcpy_s0_1 (void *d)
 {
   return memcpy (d, s0, 1);       /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" } */
 }
 
-void* test_memcpy_s0_2 (void *d)
+NOIPA void* test_memcpy_s0_2 (void *d)
 {
   return memcpy (d, s0, 2);       /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" } */
 }
 
-void* test_memcpy_s0_0_1 (void *d)
+NOIPA void* test_memcpy_s0_0_1 (void *d)
 {
   return memcpy (d, s0_0, 1);     /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" } */
 }
 
-void* test_memcpy_s0_0_2 (void *d)
+NOIPA void* test_memcpy_s0_0_2 (void *d)
 {
   return memcpy (d, s0_0, 2);     /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" } */
 }
 
 
-void* test_memcpy_s0_1_1 (void *d)
+NOIPA void* test_memcpy_s0_1_1 (void *d)
 {
   return memcpy (d, s0_1, 1);     /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" } */
 }
 
-void* test_memcpy_s0_1_2 (void *d)
+NOIPA void* test_memcpy_s0_1_2 (void *d)
 {
   return memcpy (d, s0_1, 2);     /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" } */
 }
 
 
-void* test_memcpy_s1_0_1 (void *d)
+NOIPA void* test_memcpy_s1_0_1 (void *d)
 {
   return memcpy (d, s1_0, 1);     /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" } */
 }
 
-void* test_memcpy_s1_0_2 (void *d)
+NOIPA void* test_memcpy_s1_0_2 (void *d)
 {
   return memcpy (d, s1_0, 2);     /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" } */
 }
 
 
-void* test_memmove_s0_1 (void *d)
+NOIPA void* test_memmove_s0_1 (void *d)
 {
   return memmove (d, s0, 1);      /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" } */
 }
 
-void* test_memmove_s0_2 (void *d)
+NOIPA void* test_memmove_s0_2 (void *d)
 {
   return memmove (d, s0, 2);      /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" } */
 }
 
-void* test_memmove_s0_0_1 (void *d)
+NOIPA void* test_memmove_s0_0_1 (void *d)
 {
   return memmove (d, s0_0, 1);    /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" } */
 }
 
-void* test_memmove_s0_0_2 (void *d)
+NOIPA void* test_memmove_s0_0_2 (void *d)
 {
   return memmove (d, s0_0, 2);    /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" } */
 }
@@ -90,59 +92,60 @@  const struct Empty e0_0[0][0] = { };
 const struct Empty e0_1[0][1] = { };
 const struct Empty e1_0[1][0] = { };
 
-void* test_memcpy_e_1 (void *d)
+NOIPA void* test_memcpy_e_1 (void *d)
 {
   return memcpy (d, &e, 1);       /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" } */
 }
 
-void* test_memcpy_e0_1 (void *d)
+NOIPA void* test_memcpy_e0_1 (void *d)
 {
   return memcpy (d, e0, 1);       /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" } */
 }
 
-void* test_memcpy_e0_0_1 (void *d)
+NOIPA void* test_memcpy_e0_0_1 (void *d)
 {
   return memcpy (d, e0_0, 1);     /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" } */
 }
 
-void* test_memcpy_e0_1_1 (void *d)
+NOIPA void* test_memcpy_e0_1_1 (void *d)
 {
   return memcpy (d, e0_1, 1);     /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" } */
 }
 
-void* test_memcpy_e1_0_1 (void *d)
+NOIPA void* test_memcpy_e1_0_1 (void *d)
 {
   return memcpy (d, e1_0, 1);     /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" } */
 }
 
 
-char* test_strcpy_s0 (char *d)
+NOIPA char*
+test_strcpy_s0 (char *d)          /* { dg-bogus "-Warray-bounds" "pr101679" { xfail *-*-* } } */
 {
   return strcpy (d, s0);          /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" } */
 }
 
-char* test_strcpy_s0_0 (char *d)
+NOIPA char* test_strcpy_s0_0 (char *d)
 {
   return strcpy (d, s0_0[0]);     /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" } */
 }
 
 
-char* test_strncpy_s0_1 (char *d)
+NOIPA char* test_strncpy_s0_1 (char *d)
 {
   return strncpy (d, s0, 1);    /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" } */
 }
 
-char* test_strncpy_s0_2 (char *d)
+NOIPA char* test_strncpy_s0_2 (char *d)
 {
   return strncpy (d, s0, 2);    /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" } */
 }
 
-char* test_strncpy_s0_0_1 (char *d)
+NOIPA char* test_strncpy_s0_0_1 (char *d)
 {
   return strncpy (d, s0_0[0], 1); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" } */
 }
 
-char* test_strncpy_s0_0_2 (char *d)
+NOIPA char* test_strncpy_s0_0_2 (char *d)
 {
   return strncpy (d, s0_0[0], 2); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overread" } */
 }
diff --git a/gcc/testsuite/gcc.dg/Wstring-compare-3.c b/gcc/testsuite/gcc.dg/Wstring-compare-3.c
index d4d7121dba7..6d0d5be9d77 100644
--- a/gcc/testsuite/gcc.dg/Wstring-compare-3.c
+++ b/gcc/testsuite/gcc.dg/Wstring-compare-3.c
@@ -1,6 +1,6 @@ 
 /* PR middle-end/95673 - missing -Wstring-compare for an impossible strncmp test
    { dg-do compile }
-   { dg-options "-O2 -Wall -Wstring-compare -ftrack-macro-expansion=0" } */
+   { dg-options "-O2 -Wall -Wstring-compare -Wno-stringop-overread -ftrack-macro-expansion=0" } */
 
 typedef __SIZE_TYPE__ size_t;
 
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overread-6.c b/gcc/testsuite/gcc.dg/Wstringop-overread-6.c
new file mode 100644
index 00000000000..a8177a316cc
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overread-6.c
@@ -0,0 +1,574 @@ 
+/* Verify -Wstringop-overread is issued appropriately for calls to string
+   functions at -O0 and without -Wall.
+  { dg-do compile }
+  { dg-options "-O0 -ftrack-macro-expansion=0" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+#define S2 "12"
+#define S9 "123456789"
+
+// <libint.h> functions.
+
+char* gettext (const char *);
+
+// <stdio.h> functions.
+
+typedef struct FILE FILE;
+
+int fputs (const char*, FILE*);
+int fputs_unlocked (const char*, FILE*);
+
+int puts (const char*);
+int puts_unlocked (const char*);
+
+// <string.h> functions.
+
+void* memchr (const void*, int, size_t);
+int memcmp (const void*, const void*, size_t);
+void* memcpy (void*, const void*, size_t);
+void* mempcpy (void*, const void*, size_t);
+void* memmove (void*, const void*, size_t);
+
+char* strchr (const char*, int);
+char* strrchr (const char*, int);
+
+int strcmp (const char*, const char*);
+int strncmp (const char*, const char*, size_t);
+
+char* strcat (char*, const char*);
+char* stpcpy (char*, const char*);
+char* strcpy (char*, const char*);
+char* stpncpy (char*, const char*, size_t);
+char* strncpy (char*, const char*, size_t);
+char* strdup (const char*);
+char* strndup (const char*, size_t);
+
+char* strpbrk (const char*, const char*);
+size_t strcspn (const char*, const char*);
+size_t strspn (const char*, const char*);
+char* strstr (const char*, const char*);
+
+size_t strlen (const char*);
+size_t strnlen (const char*, size_t);
+
+
+extern void* malloc (size_t);
+
+void sink (void*);
+
+
+extern char *d;
+extern char a0[0];
+
+const char arr[7] = "abc\0def";
+
+/* Unterminated array at the end of ARR above.  */
+#define unterm (arr + __builtin_strlen (arr) + 1)
+
+/* Size of the unterminated array - 1.  */
+#define unterm_size (sizeof arr - __builtin_strlen (arr) - 1)
+
+const void* nowarn_memchr (int x)
+{
+  const char *p1 = unterm;
+  return memchr (p1, x, unterm_size);
+}
+
+const void* warn_memchr (int x)
+{
+  const char *p1 = unterm;
+  return memchr (p1, x, unterm_size + 1);       // { dg-warning "-Wstringop-overread" }
+}
+
+
+void* nowarn_memcpy (void)
+{
+  const char *s = unterm;
+  return memcpy (d, s, unterm_size);
+}
+
+void* warn_memcpy (void)
+{
+  const char *s = unterm;
+  /* Use + 2 for an odd size to prevent the memmove --> MEM_REF transform
+     from defeating the warning (for now).  */
+  return memcpy (d, s, unterm_size + 2 | 1);    // { dg-warning "-Wstringop-overread" }
+}
+
+
+void* nowarn_mempcpy (void)
+{
+  const char *s = unterm;
+  return mempcpy (d, s, unterm_size);
+}
+
+void* warn_mempcpy (void)
+{
+  const char *s = unterm;
+  /* Use + 2 for an odd size to prevent the memmove --> MEM_REF transform
+     from defeating the warning (for now).  */
+  return mempcpy (d, s, unterm_size + 2 | 1);   // { dg-warning "-Wstringop-overread" }
+}
+
+
+void* nowarn_memmove (void)
+{
+  const char *s = unterm;
+  return memmove (d, s, unterm_size);
+}
+
+void* warn_memmove (void)
+{
+  const char *s = unterm;
+  /* Use + 2 for an odd size to prevent the memmove --> MEM_REF transform
+     from defeating the warning (for now).  */
+  return memmove (d, s, unterm_size + 2);       // { dg-warning "-Wstringop-overread" }
+}
+
+
+int nowarn_memcmp_1 (const char *p2)
+{
+  const char *p1 = unterm;
+  return memcmp (p1, p2, unterm_size);
+}
+
+int warn_memcmp_1 (const char *p2)
+{
+  const char *p1 = unterm;
+  return memcmp (p1, p2, unterm_size + 1);      // { dg-warning "-Wstringop-overread" }
+}
+
+int nowarn_memcmp_2 (const char *p1)
+{
+  const char *p2 = unterm;
+  return memcmp (p1, p2, unterm_size);
+}
+
+int warn_memcmp_2 (const char *p1)
+{
+  const char *p2 = unterm;
+  return memcmp (p1, p2, unterm_size + 1);      // { dg-warning "-Wstringop-overread" }
+}
+
+
+void warn_strcat (void)
+{
+  strcat (d, unterm);                   // { dg-warning "-Wstringop-overread" }
+}
+
+void warn_strcat_a0 (void)
+{
+  strcat (d, a0);                       // { dg-warning "-Wstringop-overread" }
+}
+
+void warn_strcat_end (void)
+{
+  const char *s = arr + sizeof arr;
+  strcat (d, s);                        // { dg-warning "-Wstringop-overread" }
+}
+
+char* warn_stpcpy (void)
+{
+  return stpcpy (d, unterm);            // { dg-warning "-Wstringop-overread" }
+}
+
+char* warn_stpcpy_a0 (void)
+{
+  return stpcpy (d, a0);                // { dg-warning "-Wstringop-overread" }
+}
+
+char* warn_stpcpy_end (void)
+{
+  const char *s = arr + sizeof arr;
+  return stpcpy (d, s);                 // { dg-warning "-Wstringop-overread" }
+}
+
+char* warn_stpcpy_malloc0 (void)
+{
+  char *s = malloc (0);
+  sink (s);
+  return stpcpy (d, s);                 // { dg-warning "-Wstringop-overread" }
+}
+
+
+void warn_strcpy (void)
+{
+  strcpy (d, unterm);                   // { dg-warning "-Wstringop-overread" }
+}
+
+void warn_strcpy_a0 (void)
+{
+  strcpy (d, a0);                       // { dg-warning "-Wstringop-overread" }
+}
+
+void warn_strcpy_end (void)
+{
+  const char *s = arr + sizeof arr;
+  strcpy (d, s);                        // { dg-warning "-Wstringop-overread" }
+}
+
+void warn_strcpy_malloc0 (void)
+{
+  char *s = malloc (0);
+  sink (s);
+  strcpy (d, s);                        // { dg-warning "-Wstringop-overread" }
+}
+
+
+char* nowarn_stpncpy (void)
+{
+  const char *s = unterm;
+  return stpncpy (d, s, unterm_size);
+}
+
+char* warn_stpncpy (void)
+{
+  const char *s = unterm;
+  return stpncpy (d, s, unterm_size + 1);       // { dg-warning "-Wstringop-overread" }
+}
+
+char* warn_stpncpy_a0 (void)
+{
+  return stpncpy (d, a0, 3);            // { dg-warning "-Wstringop-overread" }
+}
+
+char* warn_stpncpy_end (void)
+{
+  const char *s = arr + sizeof arr;
+  return stpncpy (d, s, sizeof arr);    // { dg-warning "-Wstringop-overread" }
+}
+
+
+void nowarn_strncpy (void)
+{
+  const char *s = unterm;
+  strncpy (d, s, unterm_size);
+}
+
+void warn_strncpy (void)
+{
+  const char *s = unterm;
+  strncpy (d, s, unterm_size + 1);      // { dg-warning "-Wstringop-overread" }
+}
+
+void warn_strncpy_a0 (void)
+{
+  const char *s = a0;
+  strncpy (d, s, sizeof arr);            // { dg-warning "-Wstringop-overread" }
+}
+
+void warn_strncpy_end (void)
+{
+  const char *s = arr + sizeof arr;
+  strncpy (d, s, sizeof arr);            // { dg-warning "-Wstringop-overread" }
+}
+
+
+int warn_strlen (void)
+{
+  return strlen (unterm);               // { dg-warning "-Wstringop-overread" }
+}
+
+int warn_strlen_a0 (void)
+{
+  return strlen (a0);                   // { dg-warning "-Wstringop-overread" }
+}
+
+int warn_strlen_end (void)
+{
+  const char *s = arr + sizeof arr;
+  return strlen (s);                    // { dg-warning "-Wstringop-overread" }
+}
+
+int warn_strlen_malloc0 (void)
+{
+  char *s = malloc (0);
+  sink (s);
+  return strlen (s);                    // { dg-warning "-Wstringop-overread" }
+}
+
+
+int nowarn_strnlen (void)
+{
+  return strnlen (unterm, unterm_size);
+}
+
+int warn_strnlen (void)
+{
+  return strnlen (unterm, unterm_size + 1);   // { dg-warning "-Wstringop-overread" }
+}
+
+int warn_strnlen_end (void)
+{
+  const char *s = arr + sizeof arr;
+  return strnlen (s, 2);                // { dg-warning "-Wstringop-overread" }
+}
+
+
+int warn_strcmp_1 (const char *s)
+{
+  return strcmp (unterm, s);            // { dg-warning "-Wstringop-overread" }
+}
+
+int warn_strcmp_2 (const char *s)
+{
+  return strcmp (s, unterm);            // { dg-warning "-Wstringop-overread" }
+}
+
+int warn_strcmp_2_end (const char *s)
+{
+  const char *t = arr + sizeof arr;
+  return strcmp (s, t);                 // { dg-warning "-Wstringop-overread" }
+}
+
+
+int nowarn_strncmp_1 (const char *s2)
+{
+  const char *s1 = unterm;
+  return strncmp (s1, s2, unterm_size);
+}
+
+int warn_strncmp_1 (const char *s2)
+{
+  const char *s1 = unterm;
+  return strncmp (s1, s2, unterm_size + 1);  // { dg-warning "-Wstringop-overread" }
+}
+
+int nowarn_strncmp_2 (const char *s1)
+{
+  const char *s2 = unterm;
+  return strncmp (s1, s2, unterm_size);
+}
+
+int warn_strncmp_2 (const char *s1)
+{
+  const char *s2 = unterm;
+  return strncmp (s1, s2, unterm_size + 1);  // { dg-warning "-Wstringop-overread" }
+}
+
+int warn_strncmp_2_end (const char *s1)
+{
+  const char *s2 = arr + sizeof arr;;
+  return strncmp (s1, s2, sizeof arr);  // { dg-warning "-Wstringop-overread" }
+}
+
+
+int nowarn_strncmp_1_s2 (void)
+{
+  /* Since the read is also bounded by the length of the S2 literal
+     and so safe, expect no warning.  */
+  const char *s = unterm;
+  return strncmp (s, S2, unterm_size + 1);   // { dg-bogus "-Wstringop-overread" "pr101778" { xfail *-*-* } }
+}
+
+int warn_strncmp_2_s2 (void)
+{
+  /* Same as above.  */
+  const char *t = unterm;
+  return strncmp (S2, t, unterm_size + 1);   // { dg-bogus "-Wstringop-overread" "pr101778" { xfail *-*-* } }
+}
+
+
+int warn_strncmp_1_s9 (void)
+{
+  /* Since both the bound and the length of the S9 literal are greater
+     than the size of UNNTERM the call reads past the end of the array.
+     Expect a warning.  */
+  const char *s1 = unterm;
+  return strncmp (s1, S9, unterm_size + 1);  // { dg-warning "-Wstringop-overread" }
+}
+
+int warn_strncmp_2_s9 (void)
+{
+  /* Same as above.  */
+  const char *s2 = unterm;
+  return strncmp (S9, s2, unterm_size + 1);  // { dg-warning "-Wstringop-overread" }
+}
+
+
+const char* warn_strchr (int x)
+{
+  return strchr (unterm, x);            // { dg-warning "-Wstringop-overread" }
+}
+
+const char* warn_strchr_end (int x)
+{
+  const char *s = arr + sizeof arr;
+  return strchr (s, x);                 // { dg-warning "-Wstringop-overread" }
+}
+
+
+const char* warn_strrchr (int x)
+{
+  return strrchr (unterm, x);           // { dg-warning "-Wstringop-overread" }
+}
+
+const char* warn_strrchr_end (int x)
+{
+  const char *s = arr + sizeof arr;
+  return strrchr (s, x);                // { dg-warning "-Wstringop-overread" }
+}
+
+
+char* warn_strdup (void)
+{
+  return strdup (unterm);               // { dg-warning "-Wstringop-overread" }
+}
+
+char* warn_strdup_end (void)
+{
+  const char *s = arr + sizeof arr;
+  return strdup (s);                    // { dg-warning "-Wstringop-overread" }
+}
+
+
+char* nowarn_strndup (void)
+{
+  return strndup (unterm, unterm_size);
+}
+
+char* warn_strndup (void)
+{
+  return strndup (unterm, unterm_size + 1); // { dg-warning "-Wstringop-overread" }
+}
+
+char* warn_strndup_end (void)
+{
+  const char *s = arr + sizeof arr;
+  return strndup (s, sizeof arr);       // { dg-warning "-Wstringop-overread" }
+}
+
+
+const char* warn_strpbrk_1 (const char *s2)
+{
+  return strpbrk (unterm, s2);          // { dg-warning "-Wstringop-overread" }
+}
+
+const char* warn_strpbrk_2 (const char *s1)
+{
+  return strpbrk (s1, unterm);          // { dg-warning "-Wstringop-overread" }
+}
+
+
+size_t warn_strspn_1 (const char *s2)
+{
+  return strspn (unterm, s2);           // { dg-warning "-Wstringop-overread" }
+}
+
+size_t warn_strspn_1_end (const char *s2)
+{
+  const char *s1 = arr + sizeof arr;
+  return strspn (s1, s2);               // { dg-warning "-Wstringop-overread" }
+}
+
+size_t warn_strspn_2 (const char *s1)
+{
+  return strspn (s1, unterm);           // { dg-warning "-Wstringop-overread" }
+}
+
+size_t warn_strspn_2_end (const char *s1)
+{
+  const char *s2 = arr + sizeof arr;
+  return strspn (s1, s2);               // { dg-warning "-Wstringop-overread" }
+}
+
+
+size_t warn_strcspn_1 (const char *s2)
+{
+  return strcspn (unterm, s2);          // { dg-warning "-Wstringop-overread" }
+}
+
+size_t warn_strcspn_1_end (const char *s2)
+{
+  const char *s1 = arr + sizeof arr;
+  return strcspn (s1, s2);              // { dg-warning "-Wstringop-overread" }
+}
+
+size_t warn_strcspn_2 (const char *s1)
+{
+  return strcspn (s1, unterm);          // { dg-warning "-Wstringop-overread" }
+}
+
+size_t warn_strcspn_2_end (const char *s1)
+{
+  const char *s2 = arr + sizeof arr;
+  return strcspn (s1, s2);              // { dg-warning "-Wstringop-overread" }
+}
+
+
+const char* warn_strstr_1 (const char *s2)
+{
+  return strstr (unterm, s2);           // { dg-warning "-Wstringop-overread" }
+}
+
+const char* warn_strstr_1_end (const char *s2)
+{
+  const char *s1 = arr + sizeof arr;
+  return strstr (s1, s2);               // { dg-warning "-Wstringop-overread" }
+}
+
+
+const char* warn_strstr_2 (const char *s1)
+{
+  return strstr (s1, unterm);           // { dg-warning "-Wstringop-overread" }
+}
+
+const char* warn_strstr_2_end (const char *s1)
+{
+  const char *s2 = arr + sizeof arr;
+  return strstr (s1, s2);               // { dg-warning "-Wstringop-overread" }
+}
+
+
+void warn_puts (void)
+{
+  puts (unterm);                        // { dg-warning "-Wstringop-overread" }
+}
+
+void warn_puts_end (void)
+{
+  const char *s = arr + sizeof arr;
+  puts (s);                             // { dg-warning "-Wstringop-overread" }
+}
+
+
+void warn_fputs (FILE *f)
+{
+  fputs (unterm, f);                    // { dg-warning "-Wstringop-overread" }
+}
+
+void warn_fputs_end (FILE *f)
+{
+  const char *s = arr + sizeof arr;
+  fputs (s, f);                         // { dg-warning "-Wstringop-overread" }
+}
+
+
+void warn_puts_unlocked (void)
+{
+  puts_unlocked (unterm);               // { dg-warning "-Wstringop-overread" }
+}
+
+void warn_puts_unlocked_end (void)
+{
+  const char *s = arr + sizeof arr;
+  puts_unlocked (s);                    // { dg-warning "-Wstringop-overread" }
+}
+
+void warn_fputs_unlocked (FILE *f)
+{
+  fputs_unlocked (unterm, f);           // { dg-warning "-Wstringop-overread" }
+}
+
+
+const char* warn_gettext (void)
+{
+  return gettext (unterm);              // { dg-warning "-Wstringop-overread" }
+}
+
+const char* warn_gettext_end (void)
+{
+  const char *s = arr + sizeof arr;
+  return gettext (s);                   // { dg-warning "-Wstringop-overread" }
+}
diff --git a/gcc/testsuite/gcc.dg/attr-nonstring-2.c b/gcc/testsuite/gcc.dg/attr-nonstring-2.c
index ba4757d673f..44a102c5a14 100644
--- a/gcc/testsuite/gcc.dg/attr-nonstring-2.c
+++ b/gcc/testsuite/gcc.dg/attr-nonstring-2.c
@@ -26,8 +26,8 @@  void test_strnlen_array_cst (void)
   T (strnlen (ns3, 1));
   T (strnlen (ns3, 2));
   T (strnlen (ns3, 3));
-  T (strnlen (ns3, 4));             /* { dg-warning "specified bound 4 exceeds source size 3" } */
-  T (strnlen (ns3, DIFF_MAX));      /* { dg-warning "specified bound \[0-9\]+ exceeds source size" } */
+  T (strnlen (ns3, 4));             /* { dg-warning "argument 1 declared attribute 'nonstring' is smaller than the specified bound 4|specified bound 4 exceeds source size 3" } */
+  T (strnlen (ns3, DIFF_MAX));      /* { dg-warning "argument 1 declared attribute 'nonstring' is smaller than the specified bound|specified bound \[0-9\]+ exceeds source size" } */
   T (strnlen (ns3, SIZE_MAX));      /* { dg-warning "specified bound \[0-9\]+ exceeds maximum object size \[0-9\]+" } */
 
   NONSTRING char ns5[5];
@@ -37,8 +37,8 @@  void test_strnlen_array_cst (void)
   T (strnlen (ns5, 1));
   T (strnlen (ns5, 2));
   T (strnlen (ns5, 3));
-  T (strnlen (ns5, 6));             /* { dg-warning "specified bound 6 exceeds source size 5" } */
-  T (strnlen (ns5, DIFF_MAX));      /* { dg-warning "specified bound \[0-9\]+ exceeds source size 5" } */
+  T (strnlen (ns5, 6));             /* { dg-warning "argument 1 declared attribute 'nonstring' is smaller than the specified bound 6|specified bound 6 exceeds source size 5" } */
+  T (strnlen (ns5, DIFF_MAX));      /* { dg-warning "argument 1 declared attribute 'nonstring' is smaller than the specified bound|specified bound \[0-9\]+ exceeds source size 5" } */
   T (strnlen (ns5, SIZE_MAX));      /* { dg-warning "specified bound \[0-9\]+ exceeds maximum object size \[0-9\]+" } */
 }
 
@@ -52,8 +52,8 @@  void test_strnlen_array_range (void)
   T (strnlen (ns3, UR (0, 9)));
   T (strnlen (ns3, UR (3, 4)));
   T (strnlen (ns3, UR (3, DIFF_MAX)));
-  T (strnlen (ns3, UR (4, 5)));     /* { dg-warning "specified bound \\\[4, 5] exceeds source size 3" } */
-  T (strnlen (ns3, UR (DIFF_MAX, SIZE_MAX)));  /* { dg-warning "specified bound \\\[\[0-9\]+, \[0-9\]+] exceeds source size 3 " } */
+  T (strnlen (ns3, UR (4, 5)));     /* { dg-warning "argument 1 declared attribute 'nonstring' is smaller than the specified bound \\\[4, 5]|specified bound \\\[4, 5] exceeds source size 3" } */
+  T (strnlen (ns3, UR (DIFF_MAX, SIZE_MAX)));  /* { dg-warning "argument 1 declared attribute 'nonstring' is smaller than the specified bound \\\[\[0-9\]+, \[0-9\]+] |specified bound \\\[\[0-9\]+, \[0-9\]+] exceeds source size 3 " } */
 }
 
 
@@ -73,8 +73,8 @@  void test_strnlen_string_cst (void)
   T (3, "12",  3, 1);
   T (3, "12",  3, 9);
   T (3, "123", 3, 1);
-  T (3, "123", 3, 4);               /* { dg-warning "specified bound 4 exceeds source size 3" } */
-  T (3, "123", 3, 9);               /* { dg-warning "specified bound 9 exceeds source size 3" } */
+  T (3, "123", 3, 4);               /* { dg-warning "argument 1 declared attribute 'nonstring' is smaller than the specified bound 4|specified bound 4 exceeds source size 3" } */
+  T (3, "123", 3, 9);               /* { dg-warning "argument 1 declared attribute 'nonstring' is smaller than the specified bound 9|specified bound 9 exceeds source size 3" } */
 
   T (5, "1",   2, 1);
   T (5, "1",   2, 2);
@@ -84,7 +84,7 @@  void test_strnlen_string_cst (void)
   T (5, "12",  3, 9);
   T (5, "123", 3, 1);
   T (5, "123", 3, 5);
-  T (5, "123", 3, 6);               /* { dg-warning "specified bound 6 exceeds source size 5" } */
+  T (5, "123", 3, 6);               /* { dg-warning "argument 1 declared attribute 'nonstring' is smaller than the specified bound 6|specified bound 6 exceeds source size 5" } */
 
   /* Strnlen shouldn't trigger a warning for arrays of unknown size
      (except for accesses to uninitialized elements when those are
@@ -110,6 +110,6 @@  void test_strnlen_string_range (void)
 {
   T (3, "1",   2, UR (0, 1));
   T (3, "1",   2, UR (3, 9));
-  T (3, "123", 3, UR (4, 5));       /* { dg-warning "specified bound \\\[4, 5] exceeds source size 3" } */
-  T (3, "123", 3, UR (5, 9));       /* { dg-warning "specified bound \\\[5, 9] exceeds source size 3" } */
+  T (3, "123", 3, UR (4, 5));       /* { dg-warning "argument 1 declared attribute 'nonstring' is smaller than the specified bound \\\[4, 5]|specified bound \\\[4, 5] exceeds source size 3" } */
+  T (3, "123", 3, UR (5, 9));       /* { dg-warning "argument 1 declared attribute 'nonstring' is smaller than the specified bound \\\[5, 9]|specified bound \\\[5, 9] exceeds source size 3" } */
 }
diff --git a/gcc/testsuite/gcc.dg/attr-nonstring-4.c b/gcc/testsuite/gcc.dg/attr-nonstring-4.c
index f2416c16e83..6f03a562026 100644
--- a/gcc/testsuite/gcc.dg/attr-nonstring-4.c
+++ b/gcc/testsuite/gcc.dg/attr-nonstring-4.c
@@ -40,7 +40,7 @@  void strnlen_cst (void)
   T (NS, /* [] */, n);
   T (NS, /* [] */, n + 1);     /* { dg-warning "specified bound \[0-9\]+ exceeds maximum object size \[0-9\]+" } */
 
-  T (NS, 9, n);                /* { dg-warning "specified bound \[0-9\]+ exceeds source size 9" } */
+  T (NS, 9, n);                /* { dg-warning "argument 1 declared attribute 'nonstring' is smaller than the specified bound \\d+|specified bound \\d+ exceeds source size 9" } */
   T (NS, 10, n + 1);           /* { dg-warning "specified bound \[0-9\]+ exceeds maximum object size \[0-9\]+" } */
 }
 
@@ -59,6 +59,6 @@  void strnlen_range (void)
   T (NS, /* [] */, n);
   T (NS, /* [] */, n + 1);     /* { dg-warning "specified bound \\\[\[0-9\]+, \[0-9\]+] exceeds maximum object size \[0-9\]+" } */
 
-  T (NS, 9, n);                /* { dg-warning "specified bound \\\[\[0-9\]+, \[0-9\]+] exceeds source size 9" } */
+  T (NS, 9, n);                /* { dg-warning "argument 1 declared attribute 'nonstring' is smaller than the specified bound \\\[\\d+, \\d+]|specified bound \\\[\\d+, \\d+] exceeds source size 9" } */
   T (NS, 10, n + 1);           /* { dg-warning "specified bound \\\[\[0-9\]+, \[0-9\]+] exceeds maximum object size \[0-9\]+" } */
 }
diff --git a/gcc/testsuite/gcc.dg/sso-14.c b/gcc/testsuite/gcc.dg/sso-14.c
index 8941946c3ab..aeff3fb16fb 100644
--- a/gcc/testsuite/gcc.dg/sso-14.c
+++ b/gcc/testsuite/gcc.dg/sso-14.c
@@ -46,10 +46,10 @@  int main(void)
   int same;
 
   msg1 = malloc (sizeof (t_s12));
-  msg2 = malloc (sizeof (t_u12));
+  msg2 = malloc (sizeof (t_s12));
 
   memset (msg1, 0, sizeof (t_s12));
-  memcpy (msg2, &msg1, sizeof (t_s12));
+  memcpy (msg2, msg1, sizeof (t_s12));
   same = memcmp (msg1, msg2, sizeof (t_s12));
 
   return 0;