]> git.ipfire.org Git - thirdparty/gcc.git/blobdiff - gcc/gimple-ssa-warn-restrict.c
Add new gimple-ssa-warn-access pass.
[thirdparty/gcc.git] / gcc / gimple-ssa-warn-restrict.c
index 6979403a4f492791d6f61cec2cc784841e40c7b2..404acb03195bab76a7976eba7196d8d46669d600 100644 (file)
@@ -1,6 +1,6 @@
 /* Pass to detect and issue warnings for violations of the restrict
    qualifier.
-   Copyright (C) 2017-2018 Free Software Foundation, Inc.
+   Copyright (C) 2017-2021 Free Software Foundation, Inc.
    Contributed by Martin Sebor <msebor@redhat.com>.
 
    This file is part of GCC.
@@ -25,9 +25,8 @@
 #include "backend.h"
 #include "tree.h"
 #include "gimple.h"
-#include "domwalk.h"
 #include "tree-pass.h"
-#include "builtins.h"
+#include "pointer-query.h"
 #include "ssa.h"
 #include "gimple-pretty-print.h"
 #include "gimple-ssa-warn-restrict.h"
 #include "gimple-iterator.h"
 #include "tree-dfa.h"
 #include "tree-ssa.h"
-#include "params.h"
 #include "tree-cfg.h"
 #include "tree-object-size.h"
 #include "calls.h"
 #include "cfgloop.h"
 #include "intl.h"
+#include "gimple-range.h"
 
 namespace {
 
@@ -75,24 +74,13 @@ class pass_wrestrict : public gimple_opt_pass
 bool
 pass_wrestrict::gate (function *fun ATTRIBUTE_UNUSED)
 {
-  return warn_array_bounds != 0 || warn_restrict != 0;
+  return warn_array_bounds || warn_restrict || warn_stringop_overflow;
 }
 
-/* Class to walk the basic blocks of a function in dominator order.  */
-class wrestrict_dom_walker : public dom_walker
-{
- public:
-  wrestrict_dom_walker () : dom_walker (CDI_DOMINATORS) {}
-
-  edge before_dom_children (basic_block) FINAL OVERRIDE;
-  bool handle_gimple_call (gimple_stmt_iterator *);
-
- private:
-  void check_call (gcall *);
-};
+static void check_call (range_query *, gimple *);
 
-edge
-wrestrict_dom_walker::before_dom_children (basic_block bb)
+static void
+wrestrict_walk (range_query *query, basic_block bb)
 {
   /* Iterate over statements, looking for function calls.  */
   for (gimple_stmt_iterator si = gsi_start_bb (bb); !gsi_end_p (si);
@@ -102,22 +90,17 @@ wrestrict_dom_walker::before_dom_children (basic_block bb)
       if (!is_gimple_call (stmt))
        continue;
 
-      if (gcall *call = as_a <gcall *> (stmt))
-       check_call (call);
+      check_call (query, stmt);
     }
-
-  return NULL;
 }
 
-/* Execute the pass for function FUN, walking in dominator order.  */
-
 unsigned
 pass_wrestrict::execute (function *fun)
 {
-  calculate_dominance_info (CDI_DOMINATORS);
-
-  wrestrict_dom_walker walker;
-  walker.walk (ENTRY_BLOCK_PTR_FOR_FN (fun));
+  gimple_ranger ranger;
+  basic_block bb;
+  FOR_EACH_BB_FN (bb, fun)
+    wrestrict_walk (&ranger, bb);
 
   return 0;
 }
@@ -125,8 +108,9 @@ pass_wrestrict::execute (function *fun)
 /* Description of a memory reference by a built-in function.  This
    is similar to ao_ref but made especially suitable for -Wrestrict
    and not for optimization.  */
-struct builtin_memref
+class builtin_memref
 {
+public:
   /* The original pointer argument to the built-in function.  */
   tree ptr;
   /* The referenced subobject or NULL if not available, and the base
@@ -137,6 +121,8 @@ struct builtin_memref
   /* The size of the BASE object, PTRDIFF_MAX if indeterminate,
      and negative until (possibly lazily) initialized.  */
   offset_int basesize;
+  /* Same for the subobject.  */
+  offset_int refsize;
 
   /* The non-negative offset of the referenced subobject.  Used to avoid
      warnings for (apparently) possibly but not definitively overlapping
@@ -148,6 +134,9 @@ struct builtin_memref
   /* The size range of the access to this reference.  */
   offset_int sizrange[2];
 
+  /* Cached result of get_max_objsize().  */
+  const offset_int maxobjsize;
+
   /* True for "bounded" string functions like strncat, and strncpy
      and their variants that specify either an exact or upper bound
      on the size of the accesses they perform.  For strncat both
@@ -155,9 +144,21 @@ struct builtin_memref
      only the destination reference is.  */
   bool strbounded_p;
 
-  builtin_memref (tree, tree);
+  builtin_memref (range_query *, gimple *, tree, tree);
 
-  tree offset_out_of_bounds (int, offset_int[2]) const;
+  tree offset_out_of_bounds (int, offset_int[3]) const;
+
+private:
+  /* Call statement to the built-in.  */
+  gimple *stmt;
+
+  range_query *query;
+
+  /* Ctor helper to set or extend OFFRANGE based on argument.  */
+  void extend_offset_range (tree);
+
+  /*  Ctor helper to determine BASE and OFFRANGE from argument.  */
+  void set_base_and_offset (tree);
 };
 
 /* Description of a memory access by a raw memory or string built-in
@@ -181,14 +182,19 @@ class builtin_access
      and false for raw memory functions.  */
   bool strict () const
   {
-    return detect_overlap != &builtin_access::generic_overlap;
+    return (detect_overlap != &builtin_access::generic_overlap
+           && detect_overlap != &builtin_access::no_overlap);
   }
 
-  builtin_access (gcall *, builtin_memref &, builtin_memref &);
+  builtin_access (range_query *, gimple *, builtin_memref &, builtin_memref &);
 
   /* Entry point to determine overlap.  */
   bool overlap ();
 
+  offset_int write_off (tree) const;
+
+  void dump (FILE *) const;
+
  private:
   /* Implementation functions used to determine overlap.  */
   bool generic_overlap ();
@@ -216,30 +222,204 @@ class builtin_access
 
 /* Initialize a memory reference representation from a pointer EXPR and
    a size SIZE in bytes.  If SIZE is NULL_TREE then the size is assumed
-   to be unknown.  */
+   to be unknown.  STMT is the statement in which expr appears in.  */
 
-builtin_memref::builtin_memref (tree expr, tree size)
+builtin_memref::builtin_memref (range_query *query, gimple *stmt, tree expr,
+                               tree size)
 : ptr (expr),
   ref (),
   base (),
   basesize (-1),
+  refsize (-1),
   refoff (HOST_WIDE_INT_MIN),
   offrange (),
   sizrange (),
-  strbounded_p ()
+  maxobjsize (tree_to_shwi (max_object_size ())),
+  strbounded_p (),
+  stmt (stmt),
+  query (query)
 {
   /* Unfortunately, wide_int default ctor is a no-op so array members
      of the type must be set individually.  */
   offrange[0] = offrange[1] = 0;
   sizrange[0] = sizrange[1] = 0;
 
-  const offset_int maxobjsize = tree_to_shwi (max_object_size ());
+  if (!expr)
+    return;
+
+  /* Find the BASE object or pointer referenced by EXPR and set
+     the offset range OFFRANGE in the process.  */
+  set_base_and_offset (expr);
+
+  if (size)
+    {
+      tree range[2];
+      /* Determine the size range, allowing for the result to be [0, 0]
+        for SIZE in the anti-range ~[0, N] where N >= PTRDIFF_MAX.  */
+      get_size_range (query, size, stmt, range, SR_ALLOW_ZERO);
+      sizrange[0] = wi::to_offset (range[0]);
+      sizrange[1] = wi::to_offset (range[1]);
+      /* get_size_range returns SIZE_MAX for the maximum size.
+        Constrain it to the real maximum of PTRDIFF_MAX.  */
+      if (sizrange[0] <= maxobjsize && sizrange[1] > maxobjsize)
+       sizrange[1] = maxobjsize;
+    }
+  else
+    sizrange[1] = maxobjsize;
+
+  if (!DECL_P (base))
+    return;
+
+  /* If the offset could be in the range of the referenced object
+     constrain its bounds so neither exceeds those of the object.  */
+  if (offrange[0] < 0 && offrange[1] > 0)
+    offrange[0] = 0;
+
+  offset_int maxoff = maxobjsize;
+  tree basetype = TREE_TYPE (base);
+  if (TREE_CODE (basetype) == ARRAY_TYPE)
+    {
+      if (ref && array_at_struct_end_p (ref))
+       ;   /* Use the maximum possible offset for last member arrays.  */
+      else if (tree basesize = TYPE_SIZE_UNIT (basetype))
+       if (TREE_CODE (basesize) == INTEGER_CST)
+         /* Size could be non-constant for a variable-length type such
+            as a struct with a VLA member (a GCC extension).  */
+         maxoff = wi::to_offset (basesize);
+    }
+
+  if (offrange[0] >= 0)
+    {
+      if (offrange[1] < 0)
+       offrange[1] = offrange[0] <= maxoff ? maxoff : maxobjsize;
+      else if (offrange[0] <= maxoff && offrange[1] > maxoff)
+       offrange[1] = maxoff;
+    }
+}
+
+/* Based on the initial length of the destination STARTLEN, returns
+   the offset of the first write access from the beginning of
+   the destination.  Nonzero only for strcat-type of calls.  */
+
+offset_int builtin_access::write_off (tree startlen) const
+{
+  if (detect_overlap != &builtin_access::strcat_overlap
+      || !startlen || TREE_CODE (startlen) != INTEGER_CST)
+    return 0;
+
+  return wi::to_offset (startlen);
+}
+
+/* Ctor helper to set or extend OFFRANGE based on the OFFSET argument.
+   Pointer offsets are represented as unsigned sizetype but must be
+   treated as signed.  */
+
+void
+builtin_memref::extend_offset_range (tree offset)
+{
+  if (TREE_CODE (offset) == INTEGER_CST)
+    {
+      offset_int off = int_cst_value (offset);
+      if (off != 0)
+       {
+         offrange[0] += off;
+         offrange[1] += off;
+       }
+      return;
+    }
+
+  if (TREE_CODE (offset) == SSA_NAME)
+    {
+      /* A pointer offset is represented as sizetype but treated
+        as signed.  */
+      wide_int min, max;
+      value_range_kind rng;
+      value_range vr;
+      if (query && query->range_of_expr (vr, offset, stmt))
+       {
+         rng = vr.kind ();
+         if (!vr.undefined_p ())
+           {
+             min = wi::to_wide (vr.min ());
+             max = wi::to_wide (vr.max ());
+           }
+       }
+      else
+       {
+         /* There is a global version here because
+            check_bounds_or_overlap may be called from gimple
+            fold during gimple lowering.  */
+         get_range_query (cfun)->range_of_expr (vr, offset, stmt);
+         rng = vr.kind ();
+         if (!vr.undefined_p ())
+           {
+             min = wi::to_wide (vr.min ());
+             max = wi::to_wide (vr.max ());
+           }
+       }
+      if (rng == VR_ANTI_RANGE && wi::lts_p (max, min))
+       {
+         /* Convert an anti-range whose upper bound is less than
+            its lower bound to a signed range.  */
+         offrange[0] += offset_int::from (max + 1, SIGNED);
+         offrange[1] += offset_int::from (min - 1, SIGNED);
+         return;
+       }
+
+      if (rng == VR_RANGE
+         && (DECL_P (base) || wi::lts_p (min, max)))
+       {
+         /* Preserve the bounds of the range for an offset into
+            a known object (it may be adjusted later relative to
+            a constant offset from its beginning).  Otherwise use
+            the bounds only when they are ascending when treated
+            as signed.  */
+         offrange[0] += offset_int::from (min, SIGNED);
+         offrange[1] += offset_int::from (max, SIGNED);
+         return;
+       }
+
+      /* Handle an anti-range the same as no range at all.  */
+      gimple *stmt = SSA_NAME_DEF_STMT (offset);
+      tree type;
+      if (is_gimple_assign (stmt)
+         && (type = TREE_TYPE (gimple_assign_rhs1 (stmt)))
+         && INTEGRAL_TYPE_P (type))
+       {
+         tree_code code = gimple_assign_rhs_code (stmt);
+         if (code == NOP_EXPR)
+           {
+             /* Use the bounds of the type of the NOP_EXPR operand
+                even if it's signed.  The result doesn't trigger
+                warnings but makes their output more readable.  */
+             offrange[0] += wi::to_offset (TYPE_MIN_VALUE (type));
+             offrange[1] += wi::to_offset (TYPE_MAX_VALUE (type));
+             return;
+           }
+       }
+    }
+
+  const offset_int maxoff = tree_to_shwi (max_object_size ()) >> 1;
+  const offset_int minoff = -maxoff - 1;
+
+  offrange[0] += minoff;
+  offrange[1] += maxoff;
+}
+
+/* Determines the base object or pointer of the reference EXPR
+   and the offset range from the beginning of the base.  */
+
+void
+builtin_memref::set_base_and_offset (tree expr)
+{
+  tree offset = NULL_TREE;
 
   if (TREE_CODE (expr) == SSA_NAME)
     {
       /* Try to tease the offset out of the pointer.  */
       gimple *stmt = SSA_NAME_DEF_STMT (expr);
-      if (gimple_assign_single_p (stmt)
+      if (!base
+         && gimple_assign_single_p (stmt)
          && gimple_assign_rhs_code (stmt) == ADDR_EXPR)
        expr = gimple_assign_rhs1 (stmt);
       else if (is_gimple_assign (stmt))
@@ -250,174 +430,151 @@ builtin_memref::builtin_memref (tree expr, tree size)
              tree rhs = gimple_assign_rhs1 (stmt);
              if (POINTER_TYPE_P (TREE_TYPE (rhs)))
                expr = gimple_assign_rhs1 (stmt);
+             else
+               {
+                 base = expr;
+                 return;
+               }
            }
          else if (code == POINTER_PLUS_EXPR)
            {
              expr = gimple_assign_rhs1 (stmt);
-
-             tree offset = gimple_assign_rhs2 (stmt);
-             if (TREE_CODE (offset) == INTEGER_CST)
-               {
-                 offset_int off = int_cst_value (offset);
-                 offrange[0] = off;
-                 offrange[1] = off;
-
-                 if (TREE_CODE (expr) == SSA_NAME)
-                   {
-                     gimple *stmt = SSA_NAME_DEF_STMT (expr);
-                     if (gimple_assign_single_p (stmt)
-                         && gimple_assign_rhs_code (stmt) == ADDR_EXPR)
-                       expr = gimple_assign_rhs1 (stmt);
-                   }
-               }
-             else if (TREE_CODE (offset) == SSA_NAME)
-               {
-                 wide_int min, max;
-                 value_range_type rng = get_range_info (offset, &min, &max);
-                 if (rng == VR_RANGE)
-                   {
-                     offrange[0] = offset_int::from (min, SIGNED);
-                     offrange[1] = offset_int::from (max, SIGNED);
-                   }
-                 else if (rng == VR_ANTI_RANGE)
-                   {
-                     offrange[0] = offset_int::from (max + 1, SIGNED);
-                     offrange[1] = offset_int::from (min - 1, SIGNED);
-                   }
-                 else
-                   {
-                     gimple *stmt = SSA_NAME_DEF_STMT (offset);
-                     tree type;
-                     if (is_gimple_assign (stmt)
-                         && gimple_assign_rhs_code (stmt) == NOP_EXPR
-                         && (type = TREE_TYPE (gimple_assign_rhs1 (stmt)))
-                         && INTEGRAL_TYPE_P (type))
-                       {
-                         /* Use the bounds of the type of the NOP_EXPR operand
-                            even if it's signed.  The result doesn't trigger
-                            warnings but makes their output more readable.  */
-                         offrange[0] = wi::to_offset (TYPE_MIN_VALUE (type));
-                         offrange[1] = wi::to_offset (TYPE_MAX_VALUE (type));
-                       }
-                     else
-                       offrange[1] = maxobjsize;
-                   }
-               }
-             else
-               offrange[1] = maxobjsize;
+             offset = gimple_assign_rhs2 (stmt);
+           }
+         else
+           {
+             base = expr;
+             return;
            }
        }
+      else
+       {
+         /* FIXME: Handle PHI nodes in case like:
+            _12 = &MEM[(void *)&a + 2B] + _10;
+
+            <bb> [local count: 1073741824]:
+            # prephitmp_13 = PHI <_12, &MEM[(void *)&a + 2B]>
+            memcpy (prephitmp_13, p_7(D), 6);  */
+         base = expr;
+         return;
+       }
     }
 
   if (TREE_CODE (expr) == ADDR_EXPR)
-    {
-      poly_int64 off;
-      tree oper = TREE_OPERAND (expr, 0);
+    expr = TREE_OPERAND (expr, 0);
 
-      /* Determine the base object or pointer of the reference
-        and its constant offset from the beginning of the base.  */
-      base = get_addr_base_and_unit_offset (oper, &off);
+  /* Stash the reference for offset validation.  */
+  ref = expr;
 
-      HOST_WIDE_INT const_off;
-      if (base && off.is_constant (&const_off))
-       {
-         offrange[0] += const_off;
-         offrange[1] += const_off;
+  poly_int64 bitsize, bitpos;
+  tree var_off;
+  machine_mode mode;
+  int sign, reverse, vol;
 
-         /* Stash the reference for offset validation.  */
-         ref = oper;
+  /* Determine the base object or pointer of the reference and
+     the constant bit offset from the beginning of the base.
+     If the offset has a non-constant component, it will be in
+     VAR_OFF.  MODE, SIGN, REVERSE, and VOL are write only and
+     unused here.  */
+  base = get_inner_reference (expr, &bitsize, &bitpos, &var_off,
+                             &mode, &sign, &reverse, &vol);
 
-         /* Also stash the constant offset for offset validation.  */
-         tree_code code = TREE_CODE (oper);
-         if (code == COMPONENT_REF)
-           {
-             tree field = TREE_OPERAND (ref, 1);
-             tree fldoff = DECL_FIELD_OFFSET (field);
-             if (TREE_CODE (fldoff) == INTEGER_CST)
-               refoff = const_off + wi::to_offset (fldoff);
-           }
-       }
-      else
-       {
-         size = NULL_TREE;
-         base = get_base_address (TREE_OPERAND (expr, 0));
-       }
-    }
+  /* get_inner_reference is not expected to return null.  */
+  gcc_assert (base != NULL);
 
-  if (!base)
-    base = build2 (MEM_REF, char_type_node, expr, null_pointer_node);
+  if (offset)
+    extend_offset_range (offset);
 
-  if (TREE_CODE (base) == MEM_REF)
+  poly_int64 bytepos = exact_div (bitpos, BITS_PER_UNIT);
+
+  /* Convert the poly_int64 offset to offset_int.  The offset
+     should be constant but be prepared for it not to be just in
+     case.  */
+  offset_int cstoff;
+  if (bytepos.is_constant (&cstoff))
+    {
+      offrange[0] += cstoff;
+      offrange[1] += cstoff;
+
+      /* Besides the reference saved above, also stash the offset
+        for validation.  */
+      if (TREE_CODE (expr) == COMPONENT_REF)
+       refoff = cstoff;
+    }
+  else
+    offrange[1] += maxobjsize;
+
+  if (var_off)
     {
-      offset_int off;
-      if (mem_ref_offset (base).is_constant (&off))
+      if (TREE_CODE (var_off) == INTEGER_CST)
        {
-         refoff += off;
-         offrange[0] += off;
-         offrange[1] += off;
+         cstoff = wi::to_offset (var_off);
+         offrange[0] += cstoff;
+         offrange[1] += cstoff;
        }
       else
-       size = NULL_TREE;
-      base = TREE_OPERAND (base, 0);
+       offrange[1] += maxobjsize;
     }
 
-  if (TREE_CODE (base) == SSA_NAME)
-    if (gimple *stmt = SSA_NAME_DEF_STMT (base))
-      {
-       enum gimple_code code = gimple_code (stmt);
-       if (code == GIMPLE_ASSIGN)
-         if (gimple_assign_rhs_code (stmt) == POINTER_PLUS_EXPR)
-           {
-             base = gimple_assign_rhs1 (stmt);
+  if (TREE_CODE (base) == MEM_REF)
+    {
+      tree memrefoff = fold_convert (ptrdiff_type_node, TREE_OPERAND (base, 1));
+      extend_offset_range (memrefoff);
+      base = TREE_OPERAND (base, 0);
 
-             tree offset = gimple_assign_rhs2 (stmt);
-             if (TREE_CODE (offset) == INTEGER_CST)
-               {
-                 offset_int off = int_cst_value (offset);
-                 refoff += off;
-                 offrange[0] += off;
-                 offrange[1] += off;
-               }
-           }
+      if (refoff != HOST_WIDE_INT_MIN
+         && TREE_CODE (expr) == COMPONENT_REF)
+       {
+         /* Bump up the offset of the referenced subobject to reflect
+            the offset to the enclosing object.  For example, so that
+            in
+              struct S { char a, b[3]; } s[2];
+              strcpy (s[1].b, "1234");
+            REFOFF is set to s[1].b - (char*)s.  */
+         offset_int off = tree_to_shwi (memrefoff);
+         refoff += off;
+       }
+
+      if (!integer_zerop (memrefoff))
+       /* A non-zero offset into an array of struct with flexible array
+          members implies that the array is empty because there is no
+          way to initialize such a member when it belongs to an array.
+          This must be some sort of a bug.  */
+       refsize = 0;
+    }
 
-       if (TREE_CODE (base) == SSA_NAME && SSA_NAME_VAR (base))
-         base = SSA_NAME_VAR (base);
-      }
+  if (TREE_CODE (ref) == COMPONENT_REF)
+    if (tree size = component_ref_size (ref))
+      if (TREE_CODE (size) == INTEGER_CST)
+       refsize = wi::to_offset (size);
 
-  if (size)
-    {
-      tree range[2];
-      /* Determine the size range, allowing for the result to be [0, 0]
-        for SIZE in the anti-range ~[0, N] where N >= PTRDIFF_MAX.  */
-      get_size_range (size, range, true);
-      sizrange[0] = wi::to_offset (range[0]);
-      sizrange[1] = wi::to_offset (range[1]);
-      /* get_size_range returns SIZE_MAX for the maximum size.
-        Constrain it to the real maximum of PTRDIFF_MAX.  */
-      if (sizrange[1] > maxobjsize)
-       sizrange[1] = maxobjsize;
-    }
-  else
-    sizrange[1] = maxobjsize;
+  if (TREE_CODE (base) == SSA_NAME)
+    set_base_and_offset (base);
 }
 
 /* Return error_mark_node if the signed offset exceeds the bounds
-   of the address space (PTRDIFF_MAX).  Otherwise, return either
-   BASE or REF when the offset exceeds the bounds of the BASE or
-   REF object, and set OOBOFF to the past-the-end offset formed
-   by the reference, including its size.  When STRICT is non-zero
-   use REF size, when available, otherwise use BASE size.  When
-   STRICT is greater than 1, use the size of the last array member
-   as the bound, otherwise treat such a member as a flexible array
-   member.  Return NULL when the offset is in bounds.  */
+   of the address space (PTRDIFF_MAX).  Otherwise, return either BASE
+   or REF when the offset exceeds the bounds of the BASE or REF object,
+   and set OOBOFF to the past-the-end offset formed by the reference,
+   including its size.  OOBOFF is initially setto the range of offsets,
+   and OOBOFF[2] to the offset of the first write access (nonzero for
+   the strcat family).  When STRICT is nonzero use REF size, when
+   available, otherwise use BASE size.  When STRICT is greater than 1,
+   use the size of the last array member as the bound, otherwise treat
+   such a member as a flexible array member.  Return NULL when the offset
+   is in bounds.  */
 
 tree
-builtin_memref::offset_out_of_bounds (int strict, offset_int ooboff[2]) const
+builtin_memref::offset_out_of_bounds (int strict, offset_int ooboff[3]) const
 {
-  const offset_int maxobjsize = tree_to_shwi (max_object_size ());
+  if (!ptr)
+    return NULL_TREE;
+
+  /* The offset of the first write access or zero.  */
+  offset_int wroff = ooboff[2];
 
   /* A temporary, possibly adjusted, copy of the offset range.  */
-  offset_int offrng[2] = { offrange[0], offrange[1] };
+  offset_int offrng[2] = { ooboff[0], ooboff[1] };
 
   if (DECL_P (base) && TREE_CODE (TREE_TYPE (base)) == ARRAY_TYPE)
     {
@@ -435,9 +592,19 @@ builtin_memref::offset_out_of_bounds (int strict, offset_int ooboff[2]) const
   bool hib = wi::les_p (offrng[0], offrng[1]);
   bool lob = !hib;
 
+  /* Set to the size remaining in the object after subtracting
+     REFOFF.  It may become negative as a result of negative indices
+     into the enclosing object, such as in:
+       extern struct S { char a[4], b[3], c[1]; } *p;
+       strcpy (p[-3].b, "123");  */
+  offset_int size = basesize;
+  tree obj = base;
+
+  const bool decl_p = DECL_P (obj);
+
   if (basesize < 0)
     {
-      endoff = offrng[lob] + sizrange[0];
+      endoff = offrng[lob] + (sizrange[0] - wroff);
 
       /* For a reference through a pointer to an object of unknown size
         all initial offsets are considered valid, positive as well as
@@ -447,41 +614,45 @@ builtin_memref::offset_out_of_bounds (int strict, offset_int ooboff[2]) const
       if (endoff > maxobjsize)
        return error_mark_node;
 
-      return NULL_TREE;
+      /* When the referenced subobject is known, the end offset must be
+        within its bounds.  Otherwise there is nothing to do.  */
+      if (strict
+         && !decl_p
+         && ref
+         && refsize >= 0
+         && TREE_CODE (ref) == COMPONENT_REF)
+       {
+         /* If REFOFF is negative, SIZE will become negative here.  */
+         size = refoff + refsize;
+         obj = ref;
+       }
+      else
+       return NULL_TREE;
     }
 
   /* A reference to an object of known size must be within the bounds
-     of the base object.  */
-  if (offrng[hib] < 0 || offrng[lob] > basesize)
-    return base;
+     of either the base object or the subobject (see above for when
+     a subobject can be used).  */
+  if ((decl_p && offrng[hib] < 0) || offrng[lob] > size)
+    return obj;
 
   /* The extent of the reference must also be within the bounds of
-     the base object (if known) or the maximum object size otherwise.  */
-  endoff = wi::smax (offrng[lob], 0) + sizrange[0];
+     the base object (if known) or the subobject or the maximum object
+     size otherwise.  */
+  endoff = offrng[lob] + sizrange[0];
   if (endoff > maxobjsize)
     return error_mark_node;
 
-  offset_int size = basesize;
-  tree obj = base;
-
   if (strict
-      && DECL_P (obj)
+      && decl_p
       && ref
-      && refoff >= 0
-      && TREE_CODE (ref) == COMPONENT_REF
-      && (strict > 1
-         || !array_at_struct_end_p (ref)))
+      && refsize >= 0
+      && TREE_CODE (ref) == COMPONENT_REF)
     {
-      /* If the reference is to a member subobject, the offset must
-        be within the bounds of the subobject.  */
-      tree field = TREE_OPERAND (ref, 1);
-      tree type = TREE_TYPE (field);
-      if (tree sz = TYPE_SIZE_UNIT (type))
-       if (TREE_CODE (sz) == INTEGER_CST)
-         {
-           size = refoff + wi::to_offset (sz);
-           obj = ref;
-         }
+      /* If the reference is to a member subobject of a declared object,
+        the offset must be within the bounds of the subobject.  */
+      size = refoff + refsize;
+      obj = ref;
     }
 
   if (endoff <= size)
@@ -489,12 +660,12 @@ builtin_memref::offset_out_of_bounds (int strict, offset_int ooboff[2]) const
 
   /* Set the out-of-bounds offset range to be one greater than
      that delimited by the reference including its size.  */
-  ooboff[lob] = size + 1;
+  ooboff[lob] = size;
 
   if (endoff > ooboff[lob])
-    ooboff[hib] = endoff;
+    ooboff[hib] = endoff - 1;
   else
-    ooboff[hib] = wi::smax (offrng[lob], 0) + sizrange[1];
+    ooboff[hib] = offrng[lob] + sizrange[1];
 
   return obj;
 }
@@ -502,13 +673,16 @@ builtin_memref::offset_out_of_bounds (int strict, offset_int ooboff[2]) const
 /* Create an association between the memory references DST and SRC
    for access by a call EXPR to a memory or string built-in funtion.  */
 
-builtin_access::builtin_access (gcall *call, builtin_memref &dst,
+builtin_access::builtin_access (range_query *query, gimple *call,
+                               builtin_memref &dst,
                                builtin_memref &src)
 : dstref (&dst), srcref (&src), sizrange (), ovloff (), ovlsiz (),
   dstoff (), srcoff (), dstsiz (), srcsiz ()
 {
+  dstoff[0] = dst.offrange[0];
+  dstoff[1] = dst.offrange[1];
+
   /* Zero out since the offset_int ctors invoked above are no-op.  */
-  dstoff[0] = dstoff[1] = 0;
   srcoff[0] = srcoff[1] = 0;
   dstsiz[0] = dstsiz[1] = 0;
   srcsiz[0] = srcsiz[1] = 0;
@@ -528,20 +702,14 @@ builtin_access::builtin_access (gcall *call, builtin_memref &dst,
 
   /* The size argument number (depends on the built-in).  */
   unsigned sizeargno = 2;
-  if (gimple_call_with_bounds_p (call))
-    sizeargno += 2;
 
   tree func = gimple_call_fndecl (call);
   switch (DECL_FUNCTION_CODE (func))
     {
     case BUILT_IN_MEMCPY:
     case BUILT_IN_MEMCPY_CHK:
-    case BUILT_IN_MEMCPY_CHKP:
-    case BUILT_IN_MEMCPY_CHK_CHKP:
     case BUILT_IN_MEMPCPY:
     case BUILT_IN_MEMPCPY_CHK:
-    case BUILT_IN_MEMPCPY_CHKP:
-    case BUILT_IN_MEMPCPY_CHK_CHKP:
       ostype = 0;
       depends_p = false;
       detect_overlap = &builtin_access::generic_overlap;
@@ -549,14 +717,20 @@ builtin_access::builtin_access (gcall *call, builtin_memref &dst,
 
     case BUILT_IN_MEMMOVE:
     case BUILT_IN_MEMMOVE_CHK:
-    case BUILT_IN_MEMMOVE_CHKP:
-    case BUILT_IN_MEMMOVE_CHK_CHKP:
       /* For memmove there is never any overlap to check for.  */
       ostype = 0;
       depends_p = false;
       detect_overlap = &builtin_access::no_overlap;
       break;
 
+    case BUILT_IN_MEMSET:
+    case BUILT_IN_MEMSET_CHK:
+      /* For memset there is never any overlap to check for.  */
+      ostype = 0;
+      depends_p = false;
+      detect_overlap = &builtin_access::no_overlap;
+      break;
+
     case BUILT_IN_STPNCPY:
     case BUILT_IN_STPNCPY_CHK:
     case BUILT_IN_STRNCPY:
@@ -567,19 +741,13 @@ builtin_access::builtin_access (gcall *call, builtin_memref &dst,
 
     case BUILT_IN_STPCPY:
     case BUILT_IN_STPCPY_CHK:
-    case BUILT_IN_STPCPY_CHKP:
-    case BUILT_IN_STPCPY_CHK_CHKP:
     case BUILT_IN_STRCPY:
     case BUILT_IN_STRCPY_CHK:
-    case BUILT_IN_STRCPY_CHKP:
-    case BUILT_IN_STRCPY_CHK_CHKP:
       detect_overlap = &builtin_access::strcpy_overlap;
       break;
 
     case BUILT_IN_STRCAT:
     case BUILT_IN_STRCAT_CHK:
-    case BUILT_IN_STRCAT_CHKP:
-    case BUILT_IN_STRCAT_CHK_CHKP:
       detect_overlap = &builtin_access::strcat_overlap;
       break;
 
@@ -593,12 +761,11 @@ builtin_access::builtin_access (gcall *call, builtin_memref &dst,
     default:
       /* Handle other string functions here whose access may need
         to be validated for in-bounds offsets and non-overlapping
-        copies.  (Not all _chkp functions have BUILT_IN_XXX_CHKP
-        macros so they need to be handled here.)  */
+        copies.  */
       return;
     }
 
-  const offset_int maxobjsize = tree_to_shwi (max_object_size ());
+  const offset_int maxobjsize = dst.maxobjsize;
 
   /* Try to determine the size of the base object.  compute_objsize
      expects a pointer so create one if BASE is a non-pointer object.  */
@@ -617,7 +784,7 @@ builtin_access::builtin_access (gcall *call, builtin_memref &dst,
        dst.basesize = maxobjsize;
     }
 
-  if (src.basesize < 0)
+  if (src.base && src.basesize < 0)
     {
       addr = src.base;
       if (!POINTER_TYPE_P (TREE_TYPE (addr)))
@@ -631,24 +798,22 @@ builtin_access::builtin_access (gcall *call, builtin_memref &dst,
        src.basesize = maxobjsize;
     }
 
-  /* If there is no dependency between the references or the base
-     objects of the two references aren't the same there's nothing
-     else to do.  */
-  if (depends_p && dstref->base != srcref->base)
-    return;
-
-  /* ...otherwise, make adjustments for references to the same object
-     by string built-in functions to reflect the constraints imposed
-     by the function.  */
+  /* Make adjustments for references to the same object by string
+     built-in functions to reflect the constraints imposed by
+     the function.  */
 
   /* For bounded string functions determine the range of the bound
      on the access.  For others, the range stays unbounded.  */
   offset_int bounds[2] = { maxobjsize, maxobjsize };
   if (dstref->strbounded_p)
     {
+      unsigned nargs = gimple_call_num_args (call);
+      if (nargs <= sizeargno)
+       return;
+
       tree size = gimple_call_arg (call, sizeargno);
       tree range[2];
-      if (get_size_range (size, range, true))
+      if (get_size_range (query, size, call, range, true))
        {
          bounds[0] = wi::to_offset (range[0]);
          bounds[1] = wi::to_offset (range[1]);
@@ -666,6 +831,7 @@ builtin_access::builtin_access (gcall *call, builtin_memref &dst,
        }
     }
 
+  bool dstsize_set = false;
   /* The size range of one reference involving the same base object
      can be determined from the size range of the other reference.
      This makes it possible to compute accurate offsets for warnings
@@ -677,11 +843,12 @@ builtin_access::builtin_access (gcall *call, builtin_memref &dst,
         the source.  */
       dstref->sizrange[0] = srcref->sizrange[0];
       dstref->sizrange[1] = srcref->sizrange[1];
+      dstsize_set = true;
     }
   else if (srcref->sizrange[0] == 0 && srcref->sizrange[1] == maxobjsize)
     {
-      /* When the source size is unknown set it to the size of
-        the destination.  */
+      /* When the size of the source access is unknown set it to the size
+        of the destination first and adjust it later if necessary.  */
       srcref->sizrange[0] = dstref->sizrange[0];
       srcref->sizrange[1] = dstref->sizrange[1];
 
@@ -689,13 +856,13 @@ builtin_access::builtin_access (gcall *call, builtin_memref &dst,
        {
          if (dstref->strbounded_p)
            {
-             /* Read access by strncpy is bounded.  */
-             if (bounds[0] < srcref->sizrange[0])
-               srcref->sizrange[0] = bounds[0];
-             if (bounds[1] < srcref->sizrange[1])
-               srcref->sizrange[1] = bounds[1];
+             /* Read access by strncpy is constrained by the third
+                argument but except for a zero bound is at least one.  */
+             srcref->sizrange[0] = bounds[1] > 0 ? 1 : 0;
+             offset_int bound = wi::umin (srcref->basesize, bounds[1]);
+             if (bound < srcref->sizrange[1])
+               srcref->sizrange[1] = bound;
            }
-
          /* For string functions, adjust the size range of the source
             reference by the inverse boundaries of the offset (because
             the higher the offset into the string the shorter its
@@ -704,7 +871,7 @@ builtin_access::builtin_access (gcall *call, builtin_memref &dst,
              && srcref->offrange[1] < srcref->sizrange[0])
            srcref->sizrange[0] -= srcref->offrange[1];
          else
-           srcref->sizrange[0] = 0;
+           srcref->sizrange[0] = 1;
 
          if (srcref->offrange[0] > 0)
            {
@@ -745,6 +912,11 @@ builtin_access::builtin_access (gcall *call, builtin_memref &dst,
            }
        }
     }
+  else if (!dstsize_set && detect_overlap == &builtin_access::strcat_overlap)
+    {
+      dstref->sizrange[0] += srcref->sizrange[0] - 1;
+      dstref->sizrange[1] += srcref->sizrange[1] - 1;
+    }
 
   if (dstref->strbounded_p)
     {
@@ -803,10 +975,9 @@ builtin_access::generic_overlap ()
 
   gcc_assert (dstref->base == srcref->base);
 
-  const offset_int maxobjsize = tree_to_shwi (max_object_size ());
+  const offset_int maxobjsize = acs.dstref->maxobjsize;
 
   offset_int maxsize = dstref->basesize < 0 ? maxobjsize : dstref->basesize;
-  gcc_assert (maxsize <= maxobjsize);
 
   /* Adjust the larger bounds of the offsets (which may be the first
      element if the lower bound is larger than the upper bound) to
@@ -865,10 +1036,26 @@ builtin_access::generic_overlap ()
      the other.  */
   bool depends_p = detect_overlap != &builtin_access::generic_overlap;
 
-  if (!overlap_certain
-      && !dstref->strbounded_p
-      && !depends_p)
-    return false;
+  if (!overlap_certain)
+    {
+      if (!dstref->strbounded_p && !depends_p)
+       /* Memcpy only considers certain overlap.  */
+       return false;
+
+      /* There's no way to distinguish an access to the same member
+        of a structure from one to two distinct members of the same
+        structure.  Give up to avoid excessive false positives.  */
+      tree basetype = TREE_TYPE (dstref->base);
+
+      if (POINTER_TYPE_P (basetype))
+       basetype = TREE_TYPE (basetype);
+      else
+       while (TREE_CODE (basetype) == ARRAY_TYPE)
+         basetype = TREE_TYPE (basetype);
+
+      if (RECORD_OR_UNION_TYPE_P (basetype))
+       return false;
+    }
 
   /* True for stpcpy and strcpy.  */
   bool stxcpy_p = (!dstref->strbounded_p
@@ -885,16 +1072,8 @@ builtin_access::generic_overlap ()
   ovloff[0] = HOST_WIDE_INT_MAX;
   ovloff[1] = HOST_WIDE_INT_MIN;
 
-  /* Adjustment to the lower bound of the offset of the overlap to
-     account for a subset of unbounded string calls where the size
-     of the destination string depends on the length of the source
-     which in turn depends on the offset into it.  */
-  bool sub1;
-
   if (stxcpy_p)
     {
-      sub1 = acs.dstoff[0] <= acs.srcoff[0];
-
       /* Iterate over the extreme locations (on the horizontal axis formed
         by their offsets) and sizes of two regions and find their smallest
         and largest overlap and the corresponding offsets.  */
@@ -927,11 +1106,9 @@ builtin_access::generic_overlap ()
     }
   else
     {
-      sub1 = !depends_p;
-
       /* Iterate over the extreme locations (on the horizontal axis
-        formed by their offsets) and sizes of two regions and find
-        their smallest and largest overlap and the corresponding
+        formed by their offsets) and sizes of the two regions and
+        find their smallest and largest overlap and the corresponding
         offsets.  */
 
       for (unsigned io = 0; io != 2; ++io)
@@ -944,15 +1121,6 @@ builtin_access::generic_overlap ()
            for (unsigned jo = 0; jo != 2; ++jo)
              for (unsigned js = 0; js != 2; ++js)
                {
-                 if (depends_p)
-                   {
-                     /* For st{p,r}ncpy the size of the source sequence
-                        depends on the offset into it.  */
-                     if (js)
-                       break;
-                     js = !jo;
-                   }
-
                  const offset_int b[2] = {
                    acs.srcoff[jo], acs.srcoff[jo] + acs.srcsiz[js]
                  };
@@ -979,8 +1147,9 @@ builtin_access::generic_overlap ()
   ovlsiz[0] = siz[0].to_shwi ();
   ovlsiz[1] = siz[1].to_shwi ();
 
+  /* Adjust the overlap offset range to reflect the overlap size range.  */
   if (ovlsiz[0] == 0 && ovlsiz[1] > 1)
-    ovloff[0] = ovloff[1] + ovlsiz[1] - 1 - sub1;
+    ovloff[1] = ovloff[0] + ovlsiz[1] - 1;
 
   return true;
 }
@@ -996,17 +1165,18 @@ builtin_access::strcat_overlap ()
 
   gcc_assert (dstref->base == srcref->base);
 
-  const offset_int maxobjsize = tree_to_shwi (max_object_size ());
+  const offset_int maxobjsize = acs.dstref->maxobjsize;
 
   gcc_assert (dstref->base && dstref->base == srcref->base);
 
   /* Adjust for strcat-like accesses.  */
 
   /* As a special case for strcat, set the DSTREF offsets to the length
-     of the source string since the function starts writing at the first
-     nul, and set the size to 1 for the length of the nul.  */
-  acs.dstoff[0] += acs.dstsiz[0];
-  acs.dstoff[1] += acs.dstsiz[1];
+     of the destination string since the function starts writing over
+     its terminating nul, and set the destination size to 1 for the length
+     of the nul.  */
+  acs.dstoff[0] += dstsiz[0] - srcref->sizrange[0];
+  acs.dstoff[1] += dstsiz[1] - srcref->sizrange[1];
 
   bool strfunc_unknown_args = acs.dstsiz[0] == 0 && acs.dstsiz[1] != 0;
 
@@ -1016,7 +1186,6 @@ builtin_access::strcat_overlap ()
   acs.dstsiz[1] = 1;
 
   offset_int maxsize = dstref->basesize < 0 ? maxobjsize : dstref->basesize;
-  gcc_assert (maxsize <= maxobjsize);
 
   /* For references to the same base object, determine if there's a pair
      of valid offsets into the two references such that access between
@@ -1079,14 +1248,36 @@ builtin_access::strcat_overlap ()
     return false;
 
   /* When strcat overlap is certain it is always a single byte:
-     the terminatinn NUL, regardless of offsets and sizes.  When
+     the terminating NUL, regardless of offsets and sizes.  When
      overlap is only possible its range is [0, 1].  */
   acs.ovlsiz[0] = dstref->sizrange[0] == dstref->sizrange[1] ? 1 : 0;
   acs.ovlsiz[1] = 1;
-  acs.ovloff[0] = (dstref->sizrange[0] + dstref->offrange[0]).to_shwi ();
-  acs.ovloff[1] = (dstref->sizrange[1] + dstref->offrange[1]).to_shwi ();
 
-  acs.sizrange[0] = wi::smax (acs.dstsiz[0], srcref->sizrange[0]).to_shwi ();
+  offset_int endoff
+    = dstref->offrange[0] + (dstref->sizrange[0] - srcref->sizrange[0]);
+  if (endoff <= srcref->offrange[0])
+    acs.ovloff[0] = wi::smin (maxobjsize, srcref->offrange[0]).to_shwi ();
+  else
+    acs.ovloff[0] = wi::smin (maxobjsize, endoff).to_shwi ();
+
+  acs.sizrange[0] = wi::smax (wi::abs (endoff - srcref->offrange[0]) + 1,
+                             srcref->sizrange[0]).to_shwi ();
+  if (dstref->offrange[0] == dstref->offrange[1])
+    {
+      if (srcref->offrange[0] == srcref->offrange[1])
+       acs.ovloff[1] = acs.ovloff[0];
+      else
+       acs.ovloff[1]
+         = wi::smin (maxobjsize,
+                     srcref->offrange[1] + srcref->sizrange[1]).to_shwi ();
+    }
+  else
+    acs.ovloff[1]
+      = wi::smin (maxobjsize,
+                 dstref->offrange[1] + dstref->sizrange[1]).to_shwi ();
+
+  if (acs.sizrange[0] == 0)
+    acs.sizrange[0] = 1;
   acs.sizrange[1] = wi::smax (acs.dstsiz[1], srcref->sizrange[1]).to_shwi ();
   return true;
 }
@@ -1099,6 +1290,27 @@ builtin_access::strcpy_overlap ()
   return generic_overlap ();
 }
 
+/* For a BASE of array type, clamp REFOFF to at most [0, BASE_SIZE]
+   if known, or [0, MAXOBJSIZE] otherwise.  */
+
+static void
+clamp_offset (tree base, offset_int refoff[2], offset_int maxobjsize)
+{
+  if (!base || TREE_CODE (TREE_TYPE (base)) != ARRAY_TYPE)
+    return;
+
+  if (refoff[0] < 0 && refoff[1] >= 0)
+    refoff[0] = 0;
+
+  if (refoff[1] < refoff[0])
+    {
+      offset_int maxsize =  maxobjsize;
+      if (tree size = TYPE_SIZE_UNIT (TREE_TYPE (base)))
+       maxsize = wi::to_offset (size);
+
+      refoff[1] = wi::umin (refoff[1], maxsize);
+    }
+}
 
 /* Return true if DSTREF and SRCREF describe accesses that either overlap
    one another or that, in order not to overlap, would imply that the size
@@ -1112,7 +1324,7 @@ builtin_access::overlap ()
 {
   builtin_access &acs = *this;
 
-  const offset_int maxobjsize = tree_to_shwi (max_object_size ());
+  const offset_int maxobjsize = dstref->maxobjsize;
 
   acs.sizrange[0] = wi::smax (dstref->sizrange[0],
                              srcref->sizrange[0]).to_shwi ();
@@ -1135,41 +1347,14 @@ builtin_access::overlap ()
   if (!dstref->base || !srcref->base)
     return false;
 
-  /* Set the access offsets.  */
-  acs.dstoff[0] = dstref->offrange[0];
-  acs.dstoff[1] = dstref->offrange[1];
-
   /* If the base object is an array adjust the bounds of the offset
      to be non-negative and within the bounds of the array if possible.  */
-  if (dstref->base
-      && TREE_CODE (TREE_TYPE (dstref->base)) == ARRAY_TYPE)
-    {
-      if (acs.dstoff[0] < 0 && acs.dstoff[1] >= 0)
-       acs.dstoff[0] = 0;
-
-      if (acs.dstoff[1] < acs.dstoff[0])
-       {
-         if (tree size = TYPE_SIZE_UNIT (TREE_TYPE (dstref->base)))
-           acs.dstoff[1] = wi::umin (acs.dstoff[1], wi::to_offset (size));
-         else
-           acs.dstoff[1] = wi::umin (acs.dstoff[1], maxobjsize);
-       }
-    }
+  clamp_offset (dstref->base, acs.dstoff, maxobjsize);
 
   acs.srcoff[0] = srcref->offrange[0];
   acs.srcoff[1] = srcref->offrange[1];
 
-  if (srcref->base
-      && TREE_CODE (TREE_TYPE (srcref->base)) == ARRAY_TYPE)
-    {
-      if (acs.srcoff[0] < 0 && acs.srcoff[1] >= 0)
-       acs.srcoff[0] = 0;
-
-      if (tree size = TYPE_SIZE_UNIT (TREE_TYPE (srcref->base)))
-       acs.srcoff[1] = wi::umin (acs.srcoff[1], wi::to_offset (size));
-      else if (acs.srcoff[1] < acs.srcoff[0])
-       acs.srcoff[1] = wi::umin (acs.srcoff[1], maxobjsize);
-    }
+  clamp_offset (srcref->base, acs.srcoff, maxobjsize);
 
   /* When the upper bound of the offset is less than the lower bound
      the former is the result of a negative offset being represented
@@ -1224,8 +1409,12 @@ builtin_access::overlap ()
   /* Call the appropriate function to determine the overlap.  */
   if ((this->*detect_overlap) ())
     {
-      sizrange[0] = wi::smax (acs.dstsiz[0], srcref->sizrange[0]).to_shwi ();
-      sizrange[1] = wi::smax (acs.dstsiz[1], srcref->sizrange[1]).to_shwi ();
+      if (!sizrange[1])
+       {
+         /* Unless the access size range has already been set, do so here.  */
+         sizrange[0] = wi::smax (acs.dstsiz[0], srcref->sizrange[0]).to_shwi ();
+         sizrange[1] = wi::smax (acs.dstsiz[1], srcref->sizrange[1]).to_shwi ();
+       }
       return true;
     }
 
@@ -1233,15 +1422,18 @@ builtin_access::overlap ()
 }
 
 /* Attempt to detect and diagnose an overlapping copy in a call expression
-   EXPR involving an an access ACS to a built-in memory or string function.
+   EXPR involving an access ACS to a built-in memory or string function.
    Return true when one has been detected, false otherwise.  */
 
 static bool
-maybe_diag_overlap (location_t loc, gcall *call, builtin_access &acs)
+maybe_diag_overlap (location_t loc, gimple *call, builtin_access &acs)
 {
   if (!acs.overlap ())
     return false;
 
+  if (warning_suppressed_p (call, OPT_Wrestrict))
+    return true;
+
   /* For convenience.  */
   const builtin_memref &dstref = *acs.dstref;
   const builtin_memref &srcref = *acs.srcref;
@@ -1286,7 +1478,7 @@ maybe_diag_overlap (location_t loc, gcall *call, builtin_access &acs)
             "[" HOST_WIDE_INT_PRINT_DEC ", " HOST_WIDE_INT_PRINT_DEC "]",
             ovloff[0], ovloff[1]);
 
-  const offset_int maxobjsize = tree_to_shwi (max_object_size ());
+  const offset_int maxobjsize = dstref.maxobjsize;
   bool must_overlap = ovlsiz[0] > 0;
 
   if (ovlsiz[1] == 0)
@@ -1302,70 +1494,66 @@ maybe_diag_overlap (location_t loc, gcall *call, builtin_access &acs)
            warning_at (loc, OPT_Wrestrict,
                        sizrange[0] == 1
                        ? (ovlsiz[0] == 1
-                          ? G_("%G%qD accessing %wu byte at offsets %s "
+                          ? G_("%qD accessing %wu byte at offsets %s "
                                "and %s overlaps %wu byte at offset %s")
-                          :  G_("%G%qD accessing %wu byte at offsets %s "
+                          :  G_("%qD accessing %wu byte at offsets %s "
                                 "and %s overlaps %wu bytes at offset "
                                 "%s"))
                        : (ovlsiz[0] == 1
-                          ? G_("%G%qD accessing %wu bytes at offsets %s "
+                          ? G_("%qD accessing %wu bytes at offsets %s "
                                "and %s overlaps %wu byte at offset %s")
-                          : G_("%G%qD accessing %wu bytes at offsets %s "
+                          : G_("%qD accessing %wu bytes at offsets %s "
                                "and %s overlaps %wu bytes at offset "
                                "%s")),
-                       call, func, sizrange[0],
+                       func, sizrange[0],
                        offstr[0], offstr[1], ovlsiz[0], offstr[2]);
          else if (ovlsiz[1] >= 0 && ovlsiz[1] < maxobjsize.to_shwi ())
-           warning_at (loc, OPT_Wrestrict,
-                       sizrange[0] == 1
-                       ? G_("%G%qD accessing %wu byte at offsets %s "
-                            "and %s overlaps between %wu and %wu bytes "
-                            "at offset %s")
-                       : G_("%G%qD accessing %wu bytes at offsets %s "
-                            "and %s overlaps between %wu and %wu bytes "
-                            "at offset %s"),
-                       call, func, sizrange[0],
-                       offstr[0], offstr[1], ovlsiz[0], ovlsiz[1],
-                       offstr[2]);
+           warning_n (loc, OPT_Wrestrict, sizrange[0],
+                      "%qD accessing %wu byte at offsets %s "
+                      "and %s overlaps between %wu and %wu bytes "
+                      "at offset %s",
+                      "%qD accessing %wu bytes at offsets %s "
+                      "and %s overlaps between %wu and %wu bytes "
+                      "at offset %s",
+                      func, sizrange[0], offstr[0], offstr[1],
+                      ovlsiz[0], ovlsiz[1], offstr[2]);
          else
-           warning_at (loc, OPT_Wrestrict,
-                       sizrange[0] == 1
-                       ? G_("%G%qD accessing %wu byte at offsets %s and "
-                            "%s overlaps %wu or more bytes at offset %s")
-                       : G_("%G%qD accessing %wu bytes at offsets %s and "
-                            "%s overlaps %wu or more bytes at offset %s"),
-                       call, func, sizrange[0],
-                       offstr[0], offstr[1], ovlsiz[0], offstr[2]);
+           warning_n (loc, OPT_Wrestrict, sizrange[0],
+                      "%qD accessing %wu byte at offsets %s and "
+                      "%s overlaps %wu or more bytes at offset %s",
+                      "%qD accessing %wu bytes at offsets %s and "
+                      "%s overlaps %wu or more bytes at offset %s",
+                      func, sizrange[0],
+                      offstr[0], offstr[1], ovlsiz[0], offstr[2]);
          return true;
        }
 
       if (sizrange[1] >= 0 && sizrange[1] < maxobjsize.to_shwi ())
        {
          if (ovlsiz[0] == ovlsiz[1])
-           warning_at (loc, OPT_Wrestrict,
-                       ovlsiz[0] == 1
-                       ? G_("%G%qD accessing between %wu and %wu bytes "
-                            "at offsets %s and %s overlaps %wu byte at "
-                            "offset %s")
-                       : G_("%G%qD accessing between %wu and %wu bytes "
-                            "at offsets %s and %s overlaps %wu bytes "
-                            "at offset %s"),
-                       call, func, sizrange[0], sizrange[1],
-                       offstr[0], offstr[1], ovlsiz[0], offstr[2]);
+           warning_n (loc, OPT_Wrestrict, ovlsiz[0],
+                      "%qD accessing between %wu and %wu bytes "
+                      "at offsets %s and %s overlaps %wu byte at "
+                      "offset %s",
+                      "%qD accessing between %wu and %wu bytes "
+                      "at offsets %s and %s overlaps %wu bytes "
+                      "at offset %s",
+                      func, sizrange[0], sizrange[1],
+                      offstr[0], offstr[1], ovlsiz[0], offstr[2]);
          else if (ovlsiz[1] >= 0 && ovlsiz[1] < maxobjsize.to_shwi ())
            warning_at (loc, OPT_Wrestrict,
-                       "%G%qD accessing between %wu and %wu bytes at "
+                       "%qD accessing between %wu and %wu bytes at "
                        "offsets %s and %s overlaps between %wu and %wu "
                        "bytes at offset %s",
-                       call, func, sizrange[0], sizrange[1],
+                       func, sizrange[0], sizrange[1],
                        offstr[0], offstr[1], ovlsiz[0], ovlsiz[1],
                        offstr[2]);
          else
            warning_at (loc, OPT_Wrestrict,
-                       "%G%qD accessing between %wu and %wu bytes at "
+                       "%qD accessing between %wu and %wu bytes at "
                        "offsets %s and %s overlaps %wu or more bytes "
                        "at offset %s",
-                       call, func, sizrange[0], sizrange[1],
+                       func, sizrange[0], sizrange[1],
                        offstr[0], offstr[1], ovlsiz[0], offstr[2]);
          return true;
        }
@@ -1374,26 +1562,25 @@ maybe_diag_overlap (location_t loc, gcall *call, builtin_access &acs)
        ovlsiz[1] = maxobjsize.to_shwi ();
 
       if (ovlsiz[0] == ovlsiz[1])
-       warning_at (loc, OPT_Wrestrict,
-                   ovlsiz[0] == 1
-                   ? G_("%G%qD accessing %wu or more bytes at offsets "
-                        "%s and %s overlaps %wu byte at offset %s")
-                   :  G_("%G%qD accessing %wu or more bytes at offsets "
-                         "%s and %s overlaps %wu bytes at offset %s"),
-                   call, func, sizrange[0], offstr[0], offstr[1],
-                   ovlsiz[0], offstr[2]);
+       warning_n (loc, OPT_Wrestrict, ovlsiz[0],
+                  "%qD accessing %wu or more bytes at offsets "
+                  "%s and %s overlaps %wu byte at offset %s",
+                  "%qD accessing %wu or more bytes at offsets "
+                  "%s and %s overlaps %wu bytes at offset %s",
+                  func, sizrange[0], offstr[0], offstr[1],
+                  ovlsiz[0], offstr[2]);
       else if (ovlsiz[1] >= 0 && ovlsiz[1] < maxobjsize.to_shwi ())
        warning_at (loc, OPT_Wrestrict,
-                   "%G%qD accessing %wu or more bytes at offsets %s "
+                   "%qD accessing %wu or more bytes at offsets %s "
                    "and %s overlaps between %wu and %wu bytes "
                    "at offset %s",
-                   call, func, sizrange[0], offstr[0], offstr[1],
+                   func, sizrange[0], offstr[0], offstr[1],
                    ovlsiz[0], ovlsiz[1], offstr[2]);
       else
        warning_at (loc, OPT_Wrestrict,
-                   "%G%qD accessing %wu or more bytes at offsets %s "
+                   "%qD accessing %wu or more bytes at offsets %s "
                    "and %s overlaps %wu or more bytes at offset %s",
-                   call, func, sizrange[0], offstr[0], offstr[1],
+                   func, sizrange[0], offstr[0], offstr[1],
                    ovlsiz[0], offstr[2]);
       return true;
     }
@@ -1401,117 +1588,164 @@ maybe_diag_overlap (location_t loc, gcall *call, builtin_access &acs)
   /* Use more concise wording when one of the offsets is unbounded
      to avoid confusing the user with large and mostly meaningless
      numbers.  */
-  bool open_range = ((dstref.offrange[0] == -maxobjsize - 1
-                     && dstref.offrange[1] == maxobjsize)
-                    || (srcref.offrange[0] == -maxobjsize - 1
-                        && srcref.offrange[1] == maxobjsize));
+  bool open_range;
+  if (DECL_P (dstref.base) && TREE_CODE (TREE_TYPE (dstref.base)) == ARRAY_TYPE)
+    open_range = ((dstref.offrange[0] == 0
+                  && dstref.offrange[1] == maxobjsize)
+                 || (srcref.offrange[0] == 0
+                     && srcref.offrange[1] == maxobjsize));
+  else
+    open_range = ((dstref.offrange[0] == -maxobjsize - 1
+                  && dstref.offrange[1] == maxobjsize)
+                 || (srcref.offrange[0] == -maxobjsize - 1
+                     && srcref.offrange[1] == maxobjsize));
 
   if (sizrange[0] == sizrange[1] || sizrange[1] == 1)
     {
       if (ovlsiz[1] == 1)
        {
          if (open_range)
-           warning_at (loc, OPT_Wrestrict,
-                       sizrange[1] == 1
-                       ? G_("%G%qD accessing %wu byte may overlap "
-                            "%wu byte")
-                       : G_("%G%qD accessing %wu bytes may overlap "
-                            "%wu byte"),
-                       call, func, sizrange[1], ovlsiz[1]);
+           warning_n (loc, OPT_Wrestrict, sizrange[1],
+                      "%qD accessing %wu byte may overlap "
+                      "%wu byte",
+                      "%qD accessing %wu bytes may overlap "
+                      "%wu byte",
+                      func, sizrange[1], ovlsiz[1]);
          else
-           warning_at (loc, OPT_Wrestrict,
-                       sizrange[1] == 1
-                       ? G_("%G%qD accessing %wu byte at offsets %s "
-                            "and %s may overlap %wu byte at offset %s")
-                       : G_("%G%qD accessing %wu bytes at offsets %s "
-                            "and %s may overlap %wu byte at offset %s"),
-                       call, func, sizrange[1], offstr[0], offstr[1],
-                       ovlsiz[1], offstr[2]);
+           warning_n (loc, OPT_Wrestrict, sizrange[1],
+                      "%qD accessing %wu byte at offsets %s "
+                      "and %s may overlap %wu byte at offset %s",
+                      "%qD accessing %wu bytes at offsets %s "
+                      "and %s may overlap %wu byte at offset %s",
+                      func, sizrange[1], offstr[0], offstr[1],
+                      ovlsiz[1], offstr[2]);
          return true;
        }
 
       if (open_range)
-       warning_at (loc, OPT_Wrestrict,
-                   sizrange[1] == 1
-                   ? G_("%G%qD accessing %wu byte may overlap "
-                        "up to %wu bytes")
-                   : G_("%G%qD accessing %wu bytes may overlap "
-                        "up to %wu bytes"),
-                   call, func, sizrange[1], ovlsiz[1]);
+       warning_n (loc, OPT_Wrestrict, sizrange[1],
+                  "%qD accessing %wu byte may overlap "
+                  "up to %wu bytes",
+                  "%qD accessing %wu bytes may overlap "
+                  "up to %wu bytes",
+                  func, sizrange[1], ovlsiz[1]);
       else
-       warning_at (loc, OPT_Wrestrict,
-                   sizrange[1] == 1
-                   ? G_("%G%qD accessing %wu byte at offsets %s and "
-                        "%s may overlap up to %wu bytes at offset %s")
-                   : G_("%G%qD accessing %wu bytes at offsets %s and "
-                        "%s may overlap up to %wu bytes at offset %s"),
-                   call, func, sizrange[1], offstr[0], offstr[1],
-                   ovlsiz[1], offstr[2]);
+       warning_n (loc, OPT_Wrestrict, sizrange[1],
+                  "%qD accessing %wu byte at offsets %s and "
+                  "%s may overlap up to %wu bytes at offset %s",
+                  "%qD accessing %wu bytes at offsets %s and "
+                  "%s may overlap up to %wu bytes at offset %s",
+                  func, sizrange[1], offstr[0], offstr[1],
+                  ovlsiz[1], offstr[2]);
       return true;
     }
 
   if (sizrange[1] >= 0 && sizrange[1] < maxobjsize.to_shwi ())
     {
       if (open_range)
-       warning_at (loc, OPT_Wrestrict,
-                   ovlsiz[1] == 1
-                   ? G_("%G%qD accessing between %wu and %wu bytes "
-                        "may overlap %wu byte")
-                   : G_("%G%qD accessing between %wu and %wu bytes "
-                        "may overlap up to %wu bytes"),
-                   call, func, sizrange[0], sizrange[1], ovlsiz[1]);
+       warning_n (loc, OPT_Wrestrict, ovlsiz[1],
+                  "%qD accessing between %wu and %wu bytes "
+                  "may overlap %wu byte",
+                  "%qD accessing between %wu and %wu bytes "
+                  "may overlap up to %wu bytes",
+                  func, sizrange[0], sizrange[1], ovlsiz[1]);
       else
-       warning_at (loc, OPT_Wrestrict,
-                   ovlsiz[1] == 1
-                   ? G_("%G%qD accessing between %wu and %wu bytes "
-                        "at offsets %s and %s may overlap %wu byte "
-                        "at offset %s")
-                   : G_("%G%qD accessing between %wu and %wu bytes "
-                        "at offsets %s and %s may overlap up to %wu "
-                        "bytes at offset %s"),
-                   call, func, sizrange[0], sizrange[1],
-                   offstr[0], offstr[1], ovlsiz[1], offstr[2]);
+       warning_n (loc, OPT_Wrestrict, ovlsiz[1],
+                  "%qD accessing between %wu and %wu bytes "
+                  "at offsets %s and %s may overlap %wu byte "
+                  "at offset %s",
+                  "%qD accessing between %wu and %wu bytes "
+                  "at offsets %s and %s may overlap up to %wu "
+                  "bytes at offset %s",
+                  func, sizrange[0], sizrange[1],
+                  offstr[0], offstr[1], ovlsiz[1], offstr[2]);
       return true;
     }
 
-  warning_at (loc, OPT_Wrestrict,
-             ovlsiz[1] == 1
-             ? G_("%G%qD accessing %wu or more bytes at offsets %s "
-                  "and %s may overlap %wu byte at offset %s")
-             : G_("%G%qD accessing %wu or more bytes at offsets %s "
-                  "and %s may overlap up to %wu bytes at offset %s"),
-             call, func, sizrange[0], offstr[0], offstr[1],
-             ovlsiz[1], offstr[2]);
+  warning_n (loc, OPT_Wrestrict, ovlsiz[1],
+            "%qD accessing %wu or more bytes at offsets %s "
+            "and %s may overlap %wu byte at offset %s",
+            "%qD accessing %wu or more bytes at offsets %s "
+            "and %s may overlap up to %wu bytes at offset %s",
+            func, sizrange[0], offstr[0], offstr[1],
+            ovlsiz[1], offstr[2]);
 
   return true;
 }
 
-/* Validate REF offsets in an EXPRession passed as an argument to a CALL
-   to a built-in function FUNC to make sure they are within the bounds
-   of the referenced object if its size is known, or PTRDIFF_MAX otherwise.
-   Both initial values of the offsets and their final value computed by
-   the function by incrementing the initial value by the size are
-   validated.  Return true if the offsets are not valid and a diagnostic
-   has been issued.  */
-
-static bool
-maybe_diag_offset_bounds (location_t loc, gcall *call, tree func, int strict,
-                         tree expr, const builtin_memref &ref)
+/* Validate REF size and offsets in an expression passed as an argument
+   to a CALL to a built-in function FUNC to make sure they are within
+   the bounds of the referenced object if its size is known, or
+   PTRDIFF_MAX otherwise.  DO_WARN is true when a diagnostic should
+   be issued, false otherwise.
+   Both initial values of the offsets and their final value computed
+   by the function by incrementing the initial value by the size are
+   validated.  Return the warning number if the offsets are not valid
+   and a diagnostic has been issued, or would have been issued if
+   DO_WARN had been true, otherwise an invalid warning number.  */
+
+static opt_code
+maybe_diag_access_bounds (gimple *call, tree func, int strict,
+                         const builtin_memref &ref, offset_int wroff,
+                         bool do_warn)
 {
-  if (!warn_array_bounds)
-    return false;
+  location_t loc = gimple_location (call);
+  const offset_int maxobjsize = ref.maxobjsize;
+
+  /* Check for excessive size first and regardless of warning options
+     since the result is used to make codegen decisions.  */
+  if (ref.sizrange[0] > maxobjsize)
+    {
+      const opt_code opt = OPT_Wstringop_overflow_;
+      /* Return true without issuing a warning.  */
+      if (!do_warn)
+       return opt;
+
+      if (ref.ref && warning_suppressed_p (ref.ref, OPT_Wstringop_overflow_))
+       return no_warning;
+
+      bool warned = false;
+      if (warn_stringop_overflow)
+       {
+         if (ref.sizrange[0] == ref.sizrange[1])
+           warned = warning_at (loc, opt,
+                                "%qD specified bound %wu "
+                                "exceeds maximum object size %wu",
+                                func, ref.sizrange[0].to_uhwi (),
+                                maxobjsize.to_uhwi ());
+         else
+           warned = warning_at (loc, opt,
+                                "%qD specified bound between %wu and %wu "
+                                "exceeds maximum object size %wu",
+                                func, ref.sizrange[0].to_uhwi (),
+                                ref.sizrange[1].to_uhwi (),
+                                maxobjsize.to_uhwi ());
+         return warned ? opt : no_warning;
+       }
+    }
 
-  offset_int ooboff[] = { ref.offrange[0], ref.offrange[1] };
+  /* Check for out-bounds pointers regardless of warning options since
+     the result is used to make codegen decisions.  An excessive WROFF
+     can only come up as a result of an invalid strncat bound and is
+     diagnosed separately using a more meaningful warning.  */
+  if (maxobjsize < wroff)
+    wroff = 0;
+  offset_int ooboff[] = { ref.offrange[0], ref.offrange[1], wroff };
   tree oobref = ref.offset_out_of_bounds (strict, ooboff);
   if (!oobref)
-    return false;
+    return no_warning;
 
-  if (EXPR_HAS_LOCATION (expr))
-    loc = EXPR_LOCATION (expr);
+  const opt_code opt = OPT_Warray_bounds;
+  /* Return true without issuing a warning.  */
+  if (!do_warn)
+    return opt;
 
-  loc = expansion_point_location_if_in_system_header (loc);
+  if (!warn_array_bounds)
+    return no_warning;
 
-  tree type;
+  if (warning_suppressed_p (ref.ptr, opt)
+      || (ref.ref && warning_suppressed_p (ref.ref, opt)))
+    return no_warning;
 
   char rangestr[2][64];
   if (ooboff[0] == ooboff[1]
@@ -1523,40 +1757,47 @@ maybe_diag_offset_bounds (location_t loc, gcall *call, tree func, int strict,
             (long long) ooboff[0].to_shwi (),
             (long long) ooboff[1].to_shwi ());
 
+  bool warned = false;
+
   if (oobref == error_mark_node)
     {
       if (ref.sizrange[0] == ref.sizrange[1])
-       sprintf (rangestr[1], "%lli", (long long) ref.sizrange[0].to_shwi ());
+       sprintf (rangestr[1], "%llu",
+                (unsigned long long) ref.sizrange[0].to_shwi ());
       else
        sprintf (rangestr[1], "[%lli, %lli]",
-                (long long) ref.sizrange[0].to_shwi (),
-                (long long) ref.sizrange[1].to_shwi ());
+                (unsigned long long) ref.sizrange[0].to_uhwi (),
+                (unsigned long long) ref.sizrange[1].to_uhwi ());
+
+      tree type;
 
       if (DECL_P (ref.base)
          && TREE_CODE (type = TREE_TYPE (ref.base)) == ARRAY_TYPE)
        {
-         if (warning_at (loc, OPT_Warray_bounds,
-                         "%G%qD pointer overflow between offset %s "
+         auto_diagnostic_group d;
+         if (warning_at (loc, opt,
+                         "%qD pointer overflow between offset %s "
                          "and size %s accessing array %qD with type %qT",
-                         call, func, rangestr[0], rangestr[1], ref.base, type))
-           inform (DECL_SOURCE_LOCATION (ref.base),
-                   "array %qD declared here", ref.base);
+                         func, rangestr[0], rangestr[1], ref.base, type))
+           {
+             inform (DECL_SOURCE_LOCATION (ref.base),
+                     "array %qD declared here", ref.base);
+             warned = true;
+           }
          else
-           warning_at (loc, OPT_Warray_bounds,
-                       "%G%qD pointer overflow between offset %s "
-                       "and size %s",
-                       call, func, rangestr[0], rangestr[1]);
+           warned = warning_at (loc, opt,
+                                "%qD pointer overflow between offset %s "
+                                "and size %s",
+                                func, rangestr[0], rangestr[1]);
        }
       else
-       warning_at (loc, OPT_Warray_bounds,
-                   "%G%qD pointer overflow between offset %s "
-                   "and size %s",
-                   call, func, rangestr[0], rangestr[1]);
+       warned = warning_at (loc, opt,
+                            "%qD pointer overflow between offset %s "
+                            "and size %s",
+                            func, rangestr[0], rangestr[1]);
     }
   else if (oobref == ref.base)
     {
-      const offset_int maxobjsize = tree_to_shwi (max_object_size ());
-
       /* True when the offset formed by an access to the reference
         is out of bounds, rather than the initial offset wich is
         in bounds.  This implies access past the end.  */
@@ -1564,85 +1805,107 @@ maybe_diag_offset_bounds (location_t loc, gcall *call, tree func, int strict,
 
       if (DECL_P (ref.base))
        {
+         auto_diagnostic_group d;
          if ((ref.basesize < maxobjsize
-              && warning_at (loc, OPT_Warray_bounds,
+              && warning_at (loc, opt,
                              form
-                             ? G_("%G%qD forming offset %s is out of "
+                             ? G_("%qD forming offset %s is out of "
                                   "the bounds [0, %wu] of object %qD with "
                                   "type %qT")
-                             : G_("%G%qD offset %s is out of the bounds "
+                             : G_("%qD offset %s is out of the bounds "
                                   "[0, %wu] of object %qD with type %qT"),
-                             call, func, rangestr[0], ref.basesize.to_uhwi (),
+                             func, rangestr[0], ref.basesize.to_uhwi (),
                              ref.base, TREE_TYPE (ref.base)))
-             || warning_at (loc, OPT_Warray_bounds,
+             || warning_at (loc, opt,
                             form
-                            ? G_("%G%qD forming offset %s is out of "
+                            ? G_("%qD forming offset %s is out of "
                                  "the bounds of object %qD with type %qT")
-                            : G_("%G%qD offset %s is out of the bounds "
+                            : G_("%qD offset %s is out of the bounds "
                                  "of object %qD with type %qT"),
-                            call, func, rangestr[0],
+                            func, rangestr[0],
                             ref.base, TREE_TYPE (ref.base)))
-           inform (DECL_SOURCE_LOCATION (ref.base),
-                   "%qD declared here", ref.base);
+           {
+             inform (DECL_SOURCE_LOCATION (ref.base),
+                     "%qD declared here", ref.base);
+             warned = true;
+           }
        }
       else if (ref.basesize < maxobjsize)
-       warning_at (loc, OPT_Warray_bounds,
-                   form
-                   ? G_("%G%qD forming offset %s is out of the bounds "
-                        "[0, %wu]")
-                   : G_("%G%qD offset %s is out of the bounds [0, %wu]"),
-                   call, func, rangestr[0], ref.basesize.to_uhwi ());
+       warned = warning_at (loc, opt,
+                            form
+                            ? G_("%qD forming offset %s is out "
+                                 "of the bounds [0, %wu]")
+                            : G_("%qD offset %s is out "
+                                 "of the bounds [0, %wu]"),
+                            func, rangestr[0], ref.basesize.to_uhwi ());
       else
-       warning_at (loc, OPT_Warray_bounds,
-                   form
-                   ? G_("%G%qD forming offset %s is out of bounds")
-                   : G_("%G%qD offset %s is out of bounds"),
-                   call, func, rangestr[0]);
+       warned = warning_at (loc, opt,
+                            form
+                            ? G_("%qD forming offset %s is out of bounds")
+                            : G_("%qD offset %s is out of bounds"),
+                            func, rangestr[0]);
     }
   else if (TREE_CODE (ref.ref) == MEM_REF)
     {
-      tree type = TREE_TYPE (TREE_OPERAND (ref.ref, 0));
+      tree refop = TREE_OPERAND (ref.ref, 0);
+      tree type = TREE_TYPE (refop);
       if (POINTER_TYPE_P (type))
        type = TREE_TYPE (type);
       type = TYPE_MAIN_VARIANT (type);
 
-      warning_at (loc, OPT_Warray_bounds,
-                 "%G%qD offset %s from the object at %qE is out "
-                 "of the bounds of %qT",
-                 call, func, rangestr[0], ref.base, type);
+      if (warning_at (loc, opt,
+                     "%qD offset %s from the object at %qE is out "
+                     "of the bounds of %qT",
+                     func, rangestr[0], ref.base, type))
+       {
+         if (TREE_CODE (ref.ref) == COMPONENT_REF)
+           refop = TREE_OPERAND (ref.ref, 1);
+         if (DECL_P (refop))
+           inform (DECL_SOURCE_LOCATION (refop),
+                   "subobject %qD declared here", refop);
+         warned = true;
+       }
     }
   else
     {
-      type = TYPE_MAIN_VARIANT (TREE_TYPE (ref.ref));
-
-      warning_at (loc, OPT_Warray_bounds,
-               "%G%qD offset %s from the object at %qE is out "
-               "of the bounds of referenced subobject %qD with type %qT "
-               "at offset %wu",
-               call, func, rangestr[0], ref.base, TREE_OPERAND (ref.ref, 1),
-               type, ref.refoff.to_uhwi ());
+      tree refop = TREE_OPERAND (ref.ref, 0);
+      tree type = TYPE_MAIN_VARIANT (TREE_TYPE (ref.ref));
+
+      if (warning_at (loc, opt,
+                     "%qD offset %s from the object at %qE is out "
+                     "of the bounds of referenced subobject %qD with "
+                     "type %qT at offset %wi",
+                     func, rangestr[0], ref.base,
+                     TREE_OPERAND (ref.ref, 1), type,
+                     ref.refoff.to_shwi ()))
+       {
+         if (TREE_CODE (ref.ref) == COMPONENT_REF)
+           refop = TREE_OPERAND (ref.ref, 1);
+         if (DECL_P (refop))
+           inform (DECL_SOURCE_LOCATION (refop),
+                   "subobject %qD declared here", refop);
+         warned = true;
+       }
     }
 
-  return true;
+  return warned ? opt : no_warning;
 }
 
 /* Check a CALL statement for restrict-violations and issue warnings
    if/when appropriate.  */
 
-void
-wrestrict_dom_walker::check_call (gcall *call)
+static void
+check_call (range_query *query, gimple *call)
 {
   /* Avoid checking the call if it has already been diagnosed for
      some reason.  */
-  if (gimple_no_warning_p (call))
+  if (warning_suppressed_p (call, OPT_Wrestrict))
     return;
 
   tree func = gimple_call_fndecl (call);
-  if (!func || DECL_BUILT_IN_CLASS (func) != BUILT_IN_NORMAL)
+  if (!func || !fndecl_built_in_p (func, BUILT_IN_NORMAL))
     return;
 
-  bool with_bounds = gimple_call_with_bounds_p (call);
-
   /* Argument number to extract from the call (depends on the built-in
      and its kind).  */
   unsigned dst_idx = -1;
@@ -1657,16 +1920,10 @@ wrestrict_dom_walker::check_call (gcall *call)
     {
     case BUILT_IN_MEMCPY:
     case BUILT_IN_MEMCPY_CHK:
-    case BUILT_IN_MEMCPY_CHKP:
-    case BUILT_IN_MEMCPY_CHK_CHKP:
     case BUILT_IN_MEMPCPY:
     case BUILT_IN_MEMPCPY_CHK:
-    case BUILT_IN_MEMPCPY_CHKP:
-    case BUILT_IN_MEMPCPY_CHK_CHKP:
     case BUILT_IN_MEMMOVE:
     case BUILT_IN_MEMMOVE_CHK:
-    case BUILT_IN_MEMMOVE_CHKP:
-    case BUILT_IN_MEMMOVE_CHK_CHKP:
       strfun = false;
       /* Fall through.  */
 
@@ -1677,31 +1934,30 @@ wrestrict_dom_walker::check_call (gcall *call)
     case BUILT_IN_STRNCPY:
     case BUILT_IN_STRNCPY_CHK:
       dst_idx = 0;
-      src_idx = 1 + with_bounds;
-      bnd_idx = 2 + 2 * with_bounds;
+      src_idx = 1;
+      bnd_idx = 2;
+      break;
+
+    case BUILT_IN_MEMSET:
+    case BUILT_IN_MEMSET_CHK:
+      dst_idx = 0;
+      bnd_idx = 2;
       break;
 
     case BUILT_IN_STPCPY:
     case BUILT_IN_STPCPY_CHK:
-    case BUILT_IN_STPCPY_CHKP:
-    case BUILT_IN_STPCPY_CHK_CHKP:
     case BUILT_IN_STRCPY:
     case BUILT_IN_STRCPY_CHK:
-    case BUILT_IN_STRCPY_CHKP:
-    case BUILT_IN_STRCPY_CHK_CHKP:
     case BUILT_IN_STRCAT:
     case BUILT_IN_STRCAT_CHK:
-    case BUILT_IN_STRCAT_CHKP:
-    case BUILT_IN_STRCAT_CHK_CHKP:
       dst_idx = 0;
-      src_idx = 1 + with_bounds;
+      src_idx = 1;
       break;
 
     default:
       /* Handle other string functions here whose access may need
         to be validated for in-bounds offsets and non-overlapping
-        copies.  (Not all _chkp functions have BUILT_IN_XXX_CHKP
-        macros so they need to be handled here.)  */
+        copies.  */
       return;
     }
 
@@ -1718,22 +1974,21 @@ wrestrict_dom_walker::check_call (gcall *call)
 
   /* DST and SRC can be null for a call with an insufficient number
      of arguments to a built-in function declared without a protype.  */
-  if (!dst || !src)
+  if (!dst || (src_idx < nargs && !src))
     return;
 
   /* DST, SRC, or DSTWR can also have the wrong type in a call to
      a function declared without a prototype.  Avoid checking such
      invalid calls.  */
   if (TREE_CODE (TREE_TYPE (dst)) != POINTER_TYPE
-      || TREE_CODE (TREE_TYPE (src)) != POINTER_TYPE
+      || (src && TREE_CODE (TREE_TYPE (src)) != POINTER_TYPE)
       || (dstwr && !INTEGRAL_TYPE_P (TREE_TYPE (dstwr))))
     return;
 
-  if (check_bounds_or_overlap (call, dst, src, dstwr, NULL_TREE))
-    return;
-
+  opt_code opt = check_bounds_or_overlap (query, call, dst, src, dstwr,
+                                         NULL_TREE);
   /* Avoid diagnosing the call again.  */
-  gimple_set_no_warning (call, true);
+  suppress_warning (call, opt);
 }
 
 } /* anonymous namespace */
@@ -1741,68 +1996,102 @@ wrestrict_dom_walker::check_call (gcall *call)
 /* Attempt to detect and diagnose invalid offset bounds and (except for
    memmove) overlapping copy in a call expression EXPR from SRC to DST
    and DSTSIZE and SRCSIZE bytes, respectively.  Both DSTSIZE and
-   SRCSIZE may be NULL.  Return false when one or the other has been
-   detected and diagnosed, true otherwise.  */
-
-bool
-check_bounds_or_overlap (gcall *call, tree dst, tree src, tree dstsize,
-                        tree srcsize, bool bounds_only /* = false */)
+   SRCSIZE may be NULL.  DO_WARN is false to detect either problem
+   without issue a warning.  Return the OPT_Wxxx constant corresponding
+   to the warning if one has been detected and zero otherwise.  */
+
+opt_code
+check_bounds_or_overlap (gimple *call, tree dst, tree src, tree dstsize,
+                        tree srcsize, bool bounds_only /* = false */,
+                        bool do_warn /* = true */)
 {
-  location_t loc = gimple_location (call);
-
-  if (tree block = gimple_block (call))
-    if (location_t *pbloc = block_nonartificial_location (block))
-      loc = *pbloc;
-
-  loc = expansion_point_location_if_in_system_header (loc);
+  return check_bounds_or_overlap (/*range_query=*/NULL,
+                                 call, dst, src, dstsize, srcsize,
+                                 bounds_only, do_warn);
+}
 
+opt_code
+check_bounds_or_overlap (range_query *query,
+                        gimple *call, tree dst, tree src, tree dstsize,
+                        tree srcsize, bool bounds_only /* = false */,
+                        bool do_warn /* = true */)
+{
   tree func = gimple_call_fndecl (call);
 
-  builtin_memref dstref (dst, dstsize);
-  builtin_memref srcref (src, srcsize);
+  builtin_memref dstref (query, call, dst, dstsize);
+  builtin_memref srcref (query, call, src, srcsize);
 
-  builtin_access acs (call, dstref, srcref);
+  /* Create a descriptor of the access.  This may adjust both DSTREF
+     and SRCREF based on one another and the kind of the access.  */
+  builtin_access acs (query, call, dstref, srcref);
 
   /* Set STRICT to the value of the -Warray-bounds=N argument for
      string functions or when N > 1.  */
   int strict = (acs.strict () || warn_array_bounds > 1 ? warn_array_bounds : 0);
 
-  /* Validate offsets first to make sure they are within the bounds
-     of the destination object if its size is known, or PTRDIFF_MAX
-     otherwise.  */
-  if (maybe_diag_offset_bounds (loc, call, func, strict, dst, dstref)
-      || maybe_diag_offset_bounds (loc, call, func, strict, src, srcref))
+  /* The starting offset of the destination write access.  Nonzero only
+     for the strcat family of functions.  */
+  offset_int wroff = acs.write_off (dstsize);
+
+  /* Validate offsets to each reference before the access first to make
+     sure they are within the bounds of the destination object if its
+     size is known, or PTRDIFF_MAX otherwise.  */
+  opt_code opt
+    = maybe_diag_access_bounds (call, func, strict, dstref, wroff, do_warn);
+  if (opt == no_warning)
+    opt = maybe_diag_access_bounds (call, func, strict, srcref, 0, do_warn);
+
+  if (opt != no_warning)
     {
-      gimple_set_no_warning (call, true);
-      return false;
+      if (do_warn)
+       suppress_warning (call, opt);
+      return opt;
     }
 
-  bool check_overlap
-    = (warn_restrict
-       && (bounds_only
-          || (DECL_FUNCTION_CODE (func) != BUILT_IN_MEMMOVE
-              && DECL_FUNCTION_CODE (func) != BUILT_IN_MEMMOVE_CHK)));
+  if (!warn_restrict || bounds_only || !src)
+    return no_warning;
 
-  if (!check_overlap)
-    return true;
+  if (!bounds_only)
+    {
+      switch (DECL_FUNCTION_CODE (func))
+       {
+       case BUILT_IN_MEMMOVE:
+       case BUILT_IN_MEMMOVE_CHK:
+       case BUILT_IN_MEMSET:
+       case BUILT_IN_MEMSET_CHK:
+         return no_warning;
+       default:
+         break;
+       }
+    }
 
+  location_t loc = gimple_location (call);
   if (operand_equal_p (dst, src, 0))
     {
-      warning_at (loc, OPT_Wrestrict,
-                 "%G%qD source argument is the same as destination",
-                 call, func);
-      gimple_set_no_warning (call, true);
-      return false;
+      /* Issue -Wrestrict unless the pointers are null (those do
+        not point to objects and so do not indicate an overlap;
+        such calls could be the result of sanitization and jump
+        threading).  */
+      if (!integer_zerop (dst) && !warning_suppressed_p (call, OPT_Wrestrict))
+       {
+         warning_at (loc, OPT_Wrestrict,
+                     "%qD source argument is the same as destination",
+                     func);
+         suppress_warning (call, OPT_Wrestrict);
+         return OPT_Wrestrict;
+       }
+
+      return no_warning;
     }
 
   /* Return false when overlap has been detected.  */
   if (maybe_diag_overlap (loc, call, acs))
     {
-      gimple_set_no_warning (call, true);
-      return false;
+      suppress_warning (call, OPT_Wrestrict);
+      return OPT_Wrestrict;
     }
 
-  return true;
+  return no_warning;
 }
 
 gimple_opt_pass *
@@ -1810,3 +2099,76 @@ make_pass_warn_restrict (gcc::context *ctxt)
 {
   return new pass_wrestrict (ctxt);
 }
+
+DEBUG_FUNCTION void
+dump_builtin_memref (FILE *fp, const builtin_memref &ref)
+{
+  fprintf (fp, "\n    ptr = ");
+  print_generic_expr (fp, ref.ptr, TDF_LINENO);
+  fprintf (fp, "\n    ref = ");
+  if (ref.ref)
+    print_generic_expr (fp, ref.ref, TDF_LINENO);
+  else
+    fputs ("null", fp);
+  fprintf (fp, "\n    base = ");
+  print_generic_expr (fp, ref.base, TDF_LINENO);
+  fprintf (fp,
+          "\n    basesize = %lli"
+          "\n    refsize = %lli"
+          "\n    refoff = %lli"
+          "\n    offrange = [%lli, %lli]"
+          "\n    sizrange = [%lli, %lli]"
+          "\n    strbounded_p = %s\n",
+          (long long)ref.basesize.to_shwi (),
+          (long long)ref.refsize.to_shwi (),
+          (long long)ref.refoff.to_shwi (),
+          (long long)ref.offrange[0].to_shwi (),
+          (long long)ref.offrange[1].to_shwi (),
+          (long long)ref.sizrange[0].to_shwi (),
+          (long long)ref.sizrange[1].to_shwi (),
+          ref.strbounded_p ? "true" : "false");
+}
+
+void
+builtin_access::dump (FILE *fp) const
+{
+  fprintf (fp, "  dstref:");
+  dump_builtin_memref (fp, *dstref);
+  fprintf (fp, "\n  srcref:");
+  dump_builtin_memref (fp, *srcref);
+
+  fprintf (fp,
+          "  sizrange = [%lli, %lli]\n"
+          "  ovloff = [%lli, %lli]\n"
+          "  ovlsiz = [%lli, %lli]\n"
+          "  dstoff = [%lli, %lli]\n"
+          "  dstsiz = [%lli, %lli]\n"
+          "  srcoff = [%lli, %lli]\n"
+          "  srcsiz = [%lli, %lli]\n",
+          (long long)sizrange[0], (long long)sizrange[1],
+          (long long)ovloff[0], (long long)ovloff[1],
+          (long long)ovlsiz[0], (long long)ovlsiz[1],
+          (long long)dstoff[0].to_shwi (), (long long)dstoff[1].to_shwi (),
+          (long long)dstsiz[0].to_shwi (), (long long)dstsiz[1].to_shwi (),
+          (long long)srcoff[0].to_shwi (), (long long)srcoff[1].to_shwi (),
+          (long long)srcsiz[0].to_shwi (), (long long)srcsiz[1].to_shwi ());
+}
+
+DEBUG_FUNCTION void
+dump_builtin_access (FILE *fp, gimple *stmt, const builtin_access &acs)
+{
+  if (stmt)
+    {
+      fprintf (fp, "\nDumping builtin_access for ");
+      print_gimple_expr (fp, stmt, TDF_LINENO);
+      fputs (":\n", fp);
+    }
+
+  acs.dump (fp);
+}
+
+DEBUG_FUNCTION void
+debug (gimple *stmt, const builtin_access &acs)
+{
+  dump_builtin_access (stdout, stmt, acs);
+}