PR tree-optimization/82945 - add warning for passing non-strings to functions that...
authorMartin Sebor <msebor@redhat.com>
Tue, 21 Nov 2017 20:01:58 +0000 (20:01 +0000)
committerMartin Sebor <msebor@gcc.gnu.org>
Tue, 21 Nov 2017 20:01:58 +0000 (13:01 -0700)
gcc/ChangeLog:

PR tree-optimization/82945
* builtins.c (expand_builtin_strlen): Call maybe_warn_nonstring_arg.
* calls.h (maybe_warn_nonstring_arg): Declare new function.
* calls.c (get_attr_nonstring_decl, maybe_warn_nonstring_arg): New
functions.
(initialize_argument_information): Call maybe_warn_nonstring_arg.
* calls.h (get_attr_nonstring_decl): Declare new function.
* doc/extend.texi (attribute nonstring): Update.
* gimple-fold.c (gimple_fold_builtin_strncpy): Call
get_attr_nonstring_decl and handle it.
* tree-ssa-strlen.c (maybe_diag_stxncpy_trunc): Same.  Improve
detection of nul-termination.
(strlen_to_stridx): Change to a pointer.
(handle_builtin_strlen, handle_builtin_stxncpy): Adjust.
(pass_strlen::execute): Same.

gcc/testsuite/ChangeLog:

PR tree-optimization/82945
* c-c++-common/Wstringop-truncation-2.c: New test.
* c-c++-common/Wstringop-truncation.c: Adjust.
* c-c++-common/attr-nonstring-2.c: Adjust.
* c-c++-common/attr-nonstring-3.c: New test.

From-SVN: r255031

12 files changed:
gcc/ChangeLog
gcc/builtins.c
gcc/calls.c
gcc/calls.h
gcc/doc/extend.texi
gcc/gimple-fold.c
gcc/testsuite/ChangeLog
gcc/testsuite/c-c++-common/Wstringop-truncation-2.c [new file with mode: 0644]
gcc/testsuite/c-c++-common/Wstringop-truncation.c
gcc/testsuite/c-c++-common/attr-nonstring-2.c
gcc/testsuite/c-c++-common/attr-nonstring-3.c [new file with mode: 0644]
gcc/tree-ssa-strlen.c

index 86b5384..2a4e92f 100644 (file)
@@ -1,3 +1,21 @@
+2017-11-21  Martin Sebor  <msebor@redhat.com>
+
+       PR tree-optimization/82945
+       * builtins.c (expand_builtin_strlen): Call maybe_warn_nonstring_arg.
+       * calls.h (maybe_warn_nonstring_arg): Declare new function.
+       * calls.c (get_attr_nonstring_decl, maybe_warn_nonstring_arg): New
+       functions.
+       (initialize_argument_information): Call maybe_warn_nonstring_arg.
+       * calls.h (get_attr_nonstring_decl): Declare new function.
+       * doc/extend.texi (attribute nonstring): Update.
+       * gimple-fold.c (gimple_fold_builtin_strncpy): Call
+       get_attr_nonstring_decl and handle it.
+       * tree-ssa-strlen.c (maybe_diag_stxncpy_trunc): Same.  Improve
+       detection of nul-termination.
+       (strlen_to_stridx): Change to a pointer.
+       (handle_builtin_strlen, handle_builtin_stxncpy): Adjust.
+       (pass_strlen::execute): Same.
+
 2017-11-21  Sergey Shalnov  <Sergey.Shalnov@intel.com>
 
        * config/i386/i386-opts.h (enum prefer_vector_width): Added new enum
index 650de0d..b974459 100644 (file)
@@ -2885,6 +2885,11 @@ expand_builtin_strlen (tree exp, rtx target,
       if (!maybe_expand_insn (icode, 4, ops))
        return NULL_RTX;
 
+      /* 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 (TREE_OPERAND (CALL_EXPR_FN (exp), 0), exp);
+
       /* Now that we are assured of success, expand the source.  */
       start_sequence ();
       pat = expand_expr (src, src_reg, Pmode, EXPAND_NORMAL);
index 3730f43..cae543c 100644 (file)
@@ -1494,6 +1494,210 @@ maybe_warn_alloc_args_overflow (tree fn, tree exp, tree args[2], int idx[2])
     }
 }
 
+/* If EXPR refers to a character array or pointer declared attribute
+   nonstring return a decl for that array or pointer and set *REF to
+   the referenced enclosing object or pointer.  Otherwise returns
+   null.  */
+
+tree
+get_attr_nonstring_decl (tree expr, tree *ref)
+{
+  tree decl = expr;
+  if (TREE_CODE (decl) == SSA_NAME)
+    {
+      gimple *def = SSA_NAME_DEF_STMT (decl);
+
+      if (is_gimple_assign (def))
+       {
+         tree_code code = gimple_assign_rhs_code (def);
+         if (code == ADDR_EXPR
+             || code == COMPONENT_REF
+             || code == VAR_DECL)
+           decl = gimple_assign_rhs1 (def);
+       }
+      else if (tree var = SSA_NAME_VAR (decl))
+       decl = var;
+    }
+
+  if (TREE_CODE (decl) == ADDR_EXPR)
+    decl = TREE_OPERAND (decl, 0);
+
+  if (ref)
+    *ref = decl;
+
+  if (TREE_CODE (decl) == COMPONENT_REF)
+    decl = TREE_OPERAND (decl, 1);
+
+  if (DECL_P (decl)
+      && lookup_attribute ("nonstring", DECL_ATTRIBUTES (decl)))
+    return decl;
+
+  return NULL_TREE;
+}
+
+/* Check the size argument to the strncmp built-in to see if it's within
+   the bounds of the arguments and if not, issue a warning.  */
+
+static void
+warn_nonstring_bound (tree fndecl, tree call)
+{
+  bool with_bounds = CALL_WITH_BOUNDS_P (call);
+
+  tree cnt = CALL_EXPR_ARG (call, with_bounds ? 4 : 2);
+
+  tree cntrange[2];
+  if (!get_size_range (cnt, cntrange))
+    return;
+
+  location_t callloc = EXPR_LOCATION (call);
+
+  for (unsigned i = 0; i != 2; ++i)
+    {
+      tree str = CALL_EXPR_ARG (call, i + 2 * with_bounds);
+
+      tree sref;
+      tree decl = get_attr_nonstring_decl (str, &sref);
+      if (!decl)
+       continue;
+
+      tree type = TREE_TYPE (decl);
+      if (TREE_CODE (type) != ARRAY_TYPE)
+       continue;
+
+      tree dom = TYPE_DOMAIN (type);
+      if (!dom)
+       continue;
+
+      tree bound = TYPE_MAX_VALUE (dom);
+      if (!bound)
+       continue;
+
+      bool warned = false;
+      if (tree_int_cst_le (bound, cntrange[0]))
+       warned = warning_at (callloc, OPT_Wstringop_truncation,
+                            "%qD argument %i declared attribute %<nonstring%> "
+                            "is smaller than the specified bound %E",
+                            fndecl, i, cntrange[0]);
+      if (warned)
+       {
+         location_t loc = DECL_SOURCE_LOCATION (decl);
+         if (loc != UNKNOWN_LOCATION)
+           inform (loc, "argument %qD declared here", decl);
+       }
+    }
+}
+
+/* Warn about passing a non-string array/pointer to a function that
+   expects a nul-terminated string argument.  */
+
+void
+maybe_warn_nonstring_arg (tree fndecl, tree exp)
+{
+  if (!fndecl || DECL_BUILT_IN_CLASS (fndecl) != BUILT_IN_NORMAL)
+    return;
+
+  bool with_bounds = CALL_WITH_BOUNDS_P (exp);
+
+  /* The bound argument to a bounded string function like strncpy.  */
+  tree bound = 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.  */
+  switch (DECL_FUNCTION_CODE (fndecl))
+    {
+    case BUILT_IN_STPNCPY:
+    case BUILT_IN_STPNCPY_CHK:
+    case BUILT_IN_STRNCMP:
+    case BUILT_IN_STRNCASECMP:
+    case BUILT_IN_STRNCPY:
+    case BUILT_IN_STRNCPY_CHK:
+      bound = CALL_EXPR_ARG (exp, with_bounds ? 4 : 2);
+      break;
+
+    case BUILT_IN_STRNDUP:
+      bound = CALL_EXPR_ARG (exp, with_bounds ? 2 : 1);
+      break;
+
+    default:
+      break;
+    }
+
+  /* Determine the range of the bound argument (if specified).  */
+  tree bndrng[2] = { NULL_TREE, NULL_TREE };
+  if (bound)
+    get_size_range (bound, bndrng);
+
+  /* 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))
+    {
+      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;
+
+      tree type = TREE_TYPE (decl);
+
+      offset_int wibnd = 0;
+      if (bndrng[0])
+       wibnd = wi::to_offset (bndrng[0]);
+
+      offset_int asize = wibnd;
+
+      if (TREE_CODE (type) == ARRAY_TYPE)
+       if (tree arrbnd = TYPE_DOMAIN (type))
+         {
+           if ((arrbnd = TYPE_MAX_VALUE (arrbnd)))
+             asize = wi::to_offset (arrbnd) + 1;
+         }
+
+      location_t loc = EXPR_LOCATION (exp);
+
+      bool warned = false;
+
+      if (wi::ltu_p (asize, wibnd))
+       warned = warning_at (loc, OPT_Wstringop_overflow_,
+                            "%qD argument %i declared attribute %<nonstring%> "
+                            "is smaller than the specified bound %E",
+                            fndecl, argno + 1, bndrng[0]);
+      else if (!bound)
+       warned = warning_at (loc, OPT_Wstringop_overflow_,
+                            "%qD argument %i declared attribute %<nonstring%>",
+                            fndecl, argno + 1);
+
+      if (warned)
+       inform (DECL_SOURCE_LOCATION (decl),
+               "argument %qD declared here", decl);
+    }
+}
+
 /* Issue an error if CALL_EXPR was flagged as requiring
    tall-call optimization.  */
 
@@ -1943,6 +2147,10 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
         alloc_size.  */
       maybe_warn_alloc_args_overflow (fndecl, exp, alloc_args, alloc_idx);
     }
+
+  /* Detect passing non-string arguments to functions expecting
+     nul-terminated strings.  */
+  maybe_warn_nonstring_arg (fndecl, exp);
 }
 
 /* Update ARGS_SIZE to contain the total size for the argument block.
index df5817f..9b7fa9a 100644 (file)
@@ -39,5 +39,7 @@ extern bool reference_callee_copied (CUMULATIVE_ARGS *, machine_mode,
                                     tree, bool);
 extern void maybe_warn_alloc_args_overflow (tree, tree, tree[2], int[2]);
 extern bool get_size_range (tree, tree[2]);
+extern tree get_attr_nonstring_decl (tree, tree * = NULL);
+extern void maybe_warn_nonstring_arg (tree, tree);
 
 #endif // GCC_CALLS_H
index 50bda91..65fc860 100644 (file)
@@ -6008,22 +6008,33 @@ types (@pxref{Common Function Attributes},
 The @code{nonstring} variable attribute specifies that an object or member
 declaration with type array of @code{char} or pointer to @code{char} is
 intended to store character arrays that do not necessarily contain
-a terminating @code{NUL} character.  This is useful to avoid warnings
-when such an array or pointer is used as an argument to a bounded string
-manipulation function such as @code{strncpy}.  For example, without the
-attribute, GCC will issue a warning for the call below because it may
-truncate the copy without appending the terminating NUL character.  Using
-the attribute makes it possible to suppress the warning.
+a terminating @code{NUL} character.  This is useful in detecting uses
+of such arrays or pointers with functions that expect NUL-terminated
+strings, and to avoid warnings when such an array or pointer is used
+as an argument to a bounded string manipulation function such as
+@code{strncpy}.  For example, without the attribute, GCC will issue
+a warning for the @code{strncpy} call below because it may truncate
+the copy without appending the terminating @code{NUL} character.  Using
+the attribute makes it possible to suppress the warning.  However, when
+the array is declared with the attribute the call to @code{strlen} is
+diagnosed because when the array doesn't contain a @code{NUL}-terminated
+string the call is undefined.  To copy, compare, of search non-string
+character arrays use the @code{memcpy}, @code{memcmp}, @code{memchr},
+and other functions that operate on arrays of bytes.  In addition,
+calling @code{strnlen} and @code{strndup} with such arrays is safe
+provided a suitable bound is specified, and not diagnosed.
 
 @smallexample
 struct Data
 @{
   char name [32] __attribute__ ((nonstring));
 @};
-void f (struct Data *pd, const char *s)
+
+int f (struct Data *pd, const char *s)
 @{
   strncpy (pd->name, s, sizeof pd->name);
   @dots{}
+  return strlen (pd->name);   // unsafe, gets a warning
 @}
 @end smallexample
 
index 1ed6383..ea8f92e 100644 (file)
@@ -62,6 +62,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "asan.h"
 #include "diagnostic-core.h"
 #include "intl.h"
+#include "calls.h"
 
 /* Return true when DECL can be referenced from current unit.
    FROM_DECL (if non-null) specify constructor of variable DECL was taken from.
@@ -1558,25 +1559,31 @@ gimple_fold_builtin_strncpy (gimple_stmt_iterator *gsi,
 {
   gimple *stmt = gsi_stmt (*gsi);
   location_t loc = gimple_location (stmt);
+  bool nonstring = get_attr_nonstring_decl (dest) != NULL_TREE;
 
   /* If the LEN parameter is zero, return DEST.  */
   if (integer_zerop (len))
     {
-      tree fndecl = gimple_call_fndecl (stmt);
-      gcall *call = as_a <gcall *> (stmt);
-
-      /* Warn about the lack of nul termination: the result is not
-        a (nul-terminated) string.  */
-      tree slen = get_maxval_strlen (src, 0);
-      if (slen && !integer_zerop (slen))
-       warning_at (loc, OPT_Wstringop_truncation,
-                   "%G%qD destination unchanged after copying no bytes "
-                   "from a string of length %E",
-                   call, fndecl, slen);
-      else
-       warning_at (loc, OPT_Wstringop_truncation,
-                   "%G%qD destination unchanged after copying no bytes",
-                   call, fndecl);
+      /* Avoid warning if the destination refers to a an array/pointer
+        decorate with attribute nonstring.  */
+      if (!nonstring)
+       {
+         tree fndecl = gimple_call_fndecl (stmt);
+         gcall *call = as_a <gcall *> (stmt);
+
+         /* Warn about the lack of nul termination: the result is not
+            a (nul-terminated) string.  */
+         tree slen = get_maxval_strlen (src, 0);
+         if (slen && !integer_zerop (slen))
+           warning_at (loc, OPT_Wstringop_truncation,
+                       "%G%qD destination unchanged after copying no bytes "
+                       "from a string of length %E",
+                       call, fndecl, slen);
+         else
+           warning_at (loc, OPT_Wstringop_truncation,
+                       "%G%qD destination unchanged after copying no bytes",
+                       call, fndecl);
+       }
 
       replace_call_with_value (gsi, dest);
       return true;
@@ -1601,53 +1608,36 @@ gimple_fold_builtin_strncpy (gimple_stmt_iterator *gsi,
   if (tree_int_cst_lt (ssize, len))
     return false;
 
-  if (tree_int_cst_lt (len, slen))
-    {
-      tree fndecl = gimple_call_fndecl (stmt);
-      gcall *call = as_a <gcall *> (stmt);
-
-      warning_at (loc, OPT_Wstringop_truncation,
-                 (tree_int_cst_equal (size_one_node, len)
-                  ? G_("%G%qD output truncated copying %E byte "
-                       "from a string of length %E")
-                  : G_("%G%qD output truncated copying %E bytes "
-                     "from a string of length %E")),
-                 call, fndecl, len, slen);
-    }
-  else if (tree_int_cst_equal (len, slen))
+  if (!nonstring)
     {
-      tree decl = dest;
-      if (TREE_CODE (decl) == SSA_NAME)
+      if (tree_int_cst_lt (len, slen))
        {
-         gimple *def_stmt = SSA_NAME_DEF_STMT (decl);
-         if (is_gimple_assign (def_stmt))
-           {
-             tree_code code = gimple_assign_rhs_code (def_stmt);
-             if (code == ADDR_EXPR || code == VAR_DECL)
-               decl = gimple_assign_rhs1 (def_stmt);
-           }
+         tree fndecl = gimple_call_fndecl (stmt);
+         gcall *call = as_a <gcall *> (stmt);
+
+         warning_at (loc, OPT_Wstringop_truncation,
+                     (tree_int_cst_equal (size_one_node, len)
+                      ? G_("%G%qD output truncated copying %E byte "
+                           "from a string of length %E")
+                      : G_("%G%qD output truncated copying %E bytes "
+                           "from a string of length %E")),
+                     call, fndecl, len, slen);
+       }
+      else if (tree_int_cst_equal (len, slen))
+       {
+         tree fndecl = gimple_call_fndecl (stmt);
+         gcall *call = as_a <gcall *> (stmt);
+
+         warning_at (loc, OPT_Wstringop_truncation,
+                     (tree_int_cst_equal (size_one_node, len)
+                      ? G_("%G%qD output truncated before terminating nul "
+                           "copying %E byte from a string of the same "
+                           "length")
+                      : G_("%G%qD output truncated before terminating nul "
+                           "copying %E bytes from a string of the same "
+                           "length")),
+                     call, fndecl, len);
        }
-
-      if (TREE_CODE (decl) == ADDR_EXPR)
-       decl = TREE_OPERAND (decl, 0);
-
-      if (TREE_CODE (decl) == COMPONENT_REF)
-       decl = TREE_OPERAND (decl, 1);
-
-      tree fndecl = gimple_call_fndecl (stmt);
-      gcall *call = as_a <gcall *> (stmt);
-
-      if (!DECL_P (decl)
-         || !lookup_attribute ("nonstring", DECL_ATTRIBUTES (decl)))
-       warning_at (loc, OPT_Wstringop_truncation,
-                   (tree_int_cst_equal (size_one_node, len)
-                    ? G_("%G%qD output truncated before terminating nul "
-                         "copying %E byte from a string of the same "
-                         "length")
-                    : G_("%G%qD output truncated before terminating nul "
-                         "copying %E bytes from a string of the same "
-                         "length")),
-                   call, fndecl, len);
     }
 
   /* OK transform into builtin memcpy.  */
index a793d57..0d86a86 100644 (file)
@@ -1,3 +1,11 @@
+2017-11-21  Martin Sebor  <msebor@redhat.com>
+
+       PR tree-optimization/82945
+       * c-c++-common/Wstringop-truncation-2.c: New test.
+       * c-c++-common/Wstringop-truncation.c: Adjust.
+       * c-c++-common/attr-nonstring-2.c: Adjust.
+       * c-c++-common/attr-nonstring-3.c: New test.
+
 2017-11-21  Sergey Shalnov  <Sergey.Shalnov@intel.com>
 
        * g++.dg/ext/pr57362.C (__attribute__): Test
diff --git a/gcc/testsuite/c-c++-common/Wstringop-truncation-2.c b/gcc/testsuite/c-c++-common/Wstringop-truncation-2.c
new file mode 100644 (file)
index 0000000..7b3c182
--- /dev/null
@@ -0,0 +1,105 @@
+/* Verify that 
+   { dg-do compile }
+   { dg-options "-O2 -Wstringop-truncation -Wno-stringop-overflow -ftrack-macro-expansion=0" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+#define stpncpy(d, s, n) __builtin_stpncpy ((d), (s), (n))
+#define strncpy(d, s, n) __builtin_stpncpy ((d), (s), (n))
+
+void sink (void*);
+
+struct A {
+  char arr[3] __attribute__ ((nonstring));
+  char str[3];
+};
+
+struct B { struct A a[3]; int i; };
+struct C { struct B b[3]; int i; };
+
+void stpncpy_arr_1 (struct C *pc, const char *s)
+{
+  stpncpy (pc->b[0].a[0].arr, s, sizeof pc->b[0].a[0].arr);
+  sink (pc->b[0].a[0].arr);
+
+  stpncpy (pc->b[0].a[1].arr, s, sizeof pc->b[0].a[1].arr);
+  sink (pc->b[0].a[1].arr);
+
+  stpncpy (pc->b[0].a[2].arr, s, sizeof pc->b[0].a[2].arr);
+  sink (pc->b[0].a[2].arr);
+
+  stpncpy (pc->b[1].a[0].arr, s, sizeof pc->b[1].a[0].arr);
+  sink (pc->b[1].a[0].arr);
+
+  stpncpy (pc->b[1].a[1].arr, s, sizeof pc->b[1].a[1].arr);
+  sink (pc->b[1].a[1].arr);
+
+  stpncpy (pc->b[1].a[2].arr, s, sizeof pc->b[1].a[2].arr);
+  sink (pc->b[1].a[2].arr);
+
+  stpncpy (pc->b[2].a[0].arr, s, sizeof pc->b[2].a[0].arr);
+  sink (pc->b[2].a[0].arr);
+
+  stpncpy (pc->b[2].a[1].arr, s, sizeof pc->b[2].a[1].arr);
+  sink (pc->b[2].a[1].arr);
+
+  stpncpy (pc->b[2].a[2].arr, s, sizeof pc->b[2].a[2].arr);
+  sink (pc->b[2].a[2].arr);
+}
+
+void stpncpy_str_nowarn_1 (struct C *pc, const char *s)
+{
+  stpncpy (pc->b[0].a[0].str, s, sizeof pc->b[0].a[0].str)[-1] = 0;   /* { dg-bogus "\\\[-Wstringop-truncation" } */
+}
+
+void stpncpy_str_nowarn_2 (struct C *pc, const char *s)
+{
+  *stpncpy (pc->b[0].a[0].str, s, sizeof pc->b[0].a[0].str - 1) = 0;   /* { dg-bogus "\\\[-Wstringop-truncation" } */
+}
+
+void stpncpy_str_nowarn_3 (struct C *pc, const char *s)
+{
+  char *d = stpncpy (pc->b[0].a[0].str, s, sizeof pc->b[0].a[0].str);   /* { dg-bogus "\\\[-Wstringop-truncation" } */
+
+  d[-1] = 0;
+}
+
+void stpncpy_str_nowarn_4 (struct C *pc, const char *s)
+{
+  char *d = stpncpy (pc->b[0].a[0].str, s, sizeof pc->b[0].a[0].str - 1);   /* { dg-bogus "\\\[-Wstringop-truncation" } */
+
+  *d = 0;
+}
+
+void strncpy_arr_1 (struct C *pc, const char *s)
+{
+  strncpy (pc->b[0].a[0].arr, s, sizeof pc->b[0].a[0].arr);
+  sink (pc->b[0].a[0].arr);
+
+  strncpy (pc->b[0].a[1].arr, s, sizeof pc->b[0].a[1].arr);
+  sink (pc->b[0].a[1].arr);
+
+  strncpy (pc->b[0].a[2].arr, s, sizeof pc->b[0].a[2].arr);
+  sink (pc->b[0].a[2].arr);
+}
+
+void strncpy_str_nowarn_1 (struct C *pc, const char *s)
+{
+  strncpy (pc->b[0].a[0].str, s, sizeof pc->b[0].a[0].str);   /* { dg-bogus "\\\[-Wstringop-truncation" } */
+
+  pc->b[0].a[0].str[sizeof pc->b[0].a[0].str - 1] = 0;
+}
+
+void strncpy_str_warn_1 (struct C *pc, const char *s)
+{
+  strncpy (pc->b[0].a[0].str, s, sizeof pc->b[0].a[0].str);   /* { dg-warning "specified bound 3 equals destination size" } */
+
+  pc->b[1].a[0].str[sizeof pc->b[0].a[0].str - 1] = 0;
+}
+
+void strncpy_str_warn_2 (struct C *pc, const char *s)
+{
+  strncpy (pc->b[0].a[0].str, s, sizeof pc->b[0].a[0].str);   /* { dg-warning "specified bound 3 equals destination size" } */
+
+  pc->b[0].a[1].str[sizeof pc->b[0].a[0].str - 1] = 0;
+}
index 7fc439f..dc8c618 100644 (file)
@@ -193,35 +193,35 @@ void test_strncpy_ptr (char *d, const char* s, const char *t, int i)
   CPY (d, CHOOSE ("123", "12"), 1); /* { dg-warning ".strncpy\[^\n\r\]* output truncated copying 1 byte from a string of length 2" } */
 
   {
-    signed char n = strlen (s);   /* { dg-message "length computed here" } */
+    signed char n = strlen (s);     /* { dg-message "length computed here" } */
     CPY (d, s, n);                  /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
   }
 
   {
-    short n = strlen (s);         /* { dg-message "length computed here" } */
+    short n = strlen (s);           /* { dg-message "length computed here" } */
     CPY (d, s, n);                  /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
   }
 
   {
-    int n = strlen (s);           /* { dg-message "length computed here" } */
+    int n = strlen (s);             /* { dg-message "length computed here" } */
     CPY (d, s, n);                  /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
   }
 
   {
-    unsigned n = strlen (s);      /* { dg-message "length computed here" } */
+    unsigned n = strlen (s);        /* { dg-message "length computed here" } */
     CPY (d, s, n);                  /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
   }
 
   {
     size_t n;
-    n = strlen (s);               /* { dg-message "length computed here" } */
+    n = strlen (s);                 /* { dg-message "length computed here" } */
     CPY (d, s, n);                  /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
   }
 
   {
     size_t n;
     char *dp2 = d + 1;
-    n = strlen (s);               /* { dg-message "length computed here" } */
+    n = strlen (s);                 /* { dg-message "length computed here" } */
     CPY (dp2, s, n);                /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
   }
 
@@ -312,9 +312,11 @@ void test_strncpy_array (Dest *pd, int i, const char* s)
   /* Exercise destination with attribute "nonstring".  */
   CPY (pd->c3ns, "", 3);
   CPY (pd->c3ns, "", 1);
-  /* Truncation is still diagnosed -- using strncpy in this case is
-     pointless and should be replaced with memcpy.  */
-  CPY (pd->c3ns, "12", 1);          /* { dg-warning "output truncated copying 1 byte from a string of length 2" } */
+  /* It could be argued that truncation in the literal case should be
+     diagnosed even for non-strings.  Using strncpy in this case is
+     pointless and should be replaced with memcpy.  But it would likely
+     be viewed as a false positive.  */
+  CPY (pd->c3ns, "12", 1);
   CPY (pd->c3ns, "12", 2);
   CPY (pd->c3ns, "12", 3);
   CPY (pd->c3ns, "123", 3);
index 6e273e7..67fce03 100644 (file)
@@ -89,7 +89,7 @@ void test_pointer (const char *s, unsigned n)
   strncpy (pns_1, "a", 1);
   strncpy (pns_2, "ab", 2);
   strncpy (pns_3, "abc", 3);
-  strncpy (pns_3, s7, 3);         /* { dg-warning "output truncated copying 3 bytes from a string of length 7" } */
+  strncpy (pns_3, s7, 3);
 
   strncpy (pns_1, s, 1);
   strncpy (pns_2, s, 1);
@@ -105,6 +105,7 @@ void test_member_array (struct MemArrays *p, const char *s, unsigned n)
 {
   const char s7[] = "1234567";
 
+  strncpy (p->ma3, "", 0);
   strncpy (p->ma3, "a", 1);
   strncpy (p->ma4, "ab", 2);
   strncpy (p->ma5, "abc", 3);
diff --git a/gcc/testsuite/c-c++-common/attr-nonstring-3.c b/gcc/testsuite/c-c++-common/attr-nonstring-3.c
new file mode 100644 (file)
index 0000000..1645ed3
--- /dev/null
@@ -0,0 +1,474 @@
+/* Test to exercise warnings when an array declared with attribute "nonstring"
+   is passed to a function that expects a nul-terminated string as an argument.
+   { dg-do compile }
+   { dg-options "-O2 -Wattributes -Wstringop-overflow -ftrack-macro-expansion=0" }  */
+
+typedef __SIZE_TYPE__       size_t;
+typedef __builtin_va_list   va_list;
+
+#if __cplusplus
+extern "C" {
+#endif
+
+void* memchr (const void*, int, size_t);
+int memcmp (const void*, const void*, size_t);
+void* memcpy (void*, const void*, size_t);
+void* memmove (void*, const void*, size_t);
+
+int printf (const char*, ...);
+int puts (const char*);
+int puts_unlocked (const char*);
+int sprintf (char*, const char*, ...);
+int snprintf (char*, size_t, const char*, ...);
+int vsprintf (char*, const char*, va_list);
+int vsnprintf (char*, size_t, const char*, va_list);
+
+int strcmp (const char*, const char*);
+int strncmp (const char*, const char*, size_t);
+
+char* stpcpy (char*, const char*);
+char* stpncpy (char*, const char*, size_t);
+
+char* strcat (char*, const char*);
+char* strncat (char*, const char*, size_t);
+
+char* strcpy (char*, const char*);
+char* strncpy (char*, const char*, size_t);
+
+char* strchr (const char*, int);
+char* strdup (const char*);
+size_t strlen (const char*);
+size_t strnlen (const char*, size_t);
+char* strndup (const char*, size_t);
+
+#if __cplusplus
+}   /* extern "C" */
+#endif
+
+#define NONSTRING __attribute__ ((nonstring))
+
+char str[4];
+char arr[4] NONSTRING;
+
+char *ptr;
+char *parr NONSTRING;
+
+struct MemArrays
+{
+  char str[4];
+  char arr[4] NONSTRING;
+  char *parr NONSTRING;
+};
+
+void sink (int, ...);
+
+
+#define T(call)  sink (0, (call))
+
+void test_printf (struct MemArrays *p)
+{
+  T (printf (str));
+  T (printf (arr));             /* { dg-warning "argument 1 declared attribute .nonstring." } */
+
+  T (printf (ptr));
+  T (printf (parr));            /* { dg-warning "argument 1 declared attribute .nonstring." } */
+
+  T (printf (p->str));
+  T (printf (p->arr));          /* { dg-warning "argument 1 declared attribute .nonstring." } */
+}
+
+
+void test_puts (struct MemArrays *p)
+{
+  T (puts (str));
+  T (puts (arr));               /* { dg-warning "argument 1 declared attribute .nonstring." } */
+
+  T (puts (ptr));
+  T (puts (parr));              /* { dg-warning "argument 1 declared attribute .nonstring." } */
+
+  T (puts (p->str));
+  T (puts (p->arr));            /* { dg-warning "argument 1 declared attribute .nonstring." } */
+}
+
+
+void test_snprintf (char *d, size_t n, struct MemArrays *p)
+{
+  T (snprintf (d, n, str));
+  T (snprintf (d, n, arr));       /* { dg-warning "argument 3 declared attribute .nonstring." } */
+
+  T (snprintf (d, n, ptr));
+  T (snprintf (d, n, parr));      /* { dg-warning "argument 3 declared attribute .nonstring." } */
+
+  T (snprintf (d, n, p->str));
+  T (snprintf (d, n, p->arr));    /* { dg-warning "argument 3 declared attribute .nonstring." } */
+}
+
+
+void test_sprintf (char *d, struct MemArrays *p)
+{
+  T (sprintf (d, str));
+  T (sprintf (d, arr));           /* { dg-warning "argument 2 declared attribute .nonstring." } */
+
+  T (sprintf (d, ptr));
+  T (sprintf (d, parr));          /* { dg-warning "argument 2 declared attribute .nonstring." } */
+
+  T (sprintf (d, p->str));
+  T (sprintf (d, p->arr));        /* { dg-warning "argument 2 declared attribute .nonstring." } */
+}
+
+
+void test_vsnprintf (char *d, size_t n, struct MemArrays *p, va_list va)
+{
+  T (vsnprintf (d, n, str, va));
+  T (vsnprintf (d, n, arr, va));  /* { dg-warning "argument 3 declared attribute .nonstring." } */
+
+  T (vsnprintf (d, n, ptr, va));
+  T (vsnprintf (d, n, parr, va)); /* { dg-warning "argument 3 declared attribute .nonstring." } */
+
+  T (vsnprintf (d, n, p->str, va));
+  T (vsnprintf (d, n, p->arr, va)); /* { dg-warning "argument 3 declared attribute .nonstring." } */
+}
+
+
+void test_vsprintf (char *d, struct MemArrays *p, va_list va)
+{
+  T (vsprintf (d, str, va));
+  T (vsprintf (d, arr, va));      /* { dg-warning "argument 2 declared attribute .nonstring." } */
+
+  T (vsprintf (d, ptr, va));
+  T (vsprintf (d, parr, va));     /* { dg-warning "argument 2 declared attribute .nonstring." } */
+
+  T (vsprintf (d, p->str, va));
+  T (vsprintf (d, p->arr, va));   /* { dg-warning "argument 2 declared attribute .nonstring." } */
+}
+
+
+void test_strcmp (struct MemArrays *p)
+{
+  T (strcmp (str, str));
+  T (strcmp (str, arr));          /* { dg-warning "argument 2 declared attribute .nonstring." } */
+  T (strcmp (arr, str));          /* { dg-warning "argument 1 declared attribute .nonstring." } */
+
+  T (strcmp (str, ptr));
+  T (strcmp (str, parr));         /* { dg-warning "argument 2 declared attribute .nonstring." } */
+  T (strcmp (parr, str));         /* { dg-warning "argument 1 declared attribute .nonstring." } */
+
+  T (strcmp (p->str, p->arr));    /* { dg-warning "argument 2 declared attribute .nonstring." } */
+  T (strcmp (p->arr, p->str));    /* { dg-warning "argument 1 declared attribute .nonstring." } */
+  T (strcmp (p->parr, p->str));   /* { dg-warning "argument 1 declared attribute .nonstring." } */
+}
+
+
+void test_strncmp_warn (struct MemArrays *p)
+{
+  enum { N = sizeof arr };
+  T (strncmp (str, arr, N));
+  T (strncmp (arr, str, N));
+
+  T (strncmp (str, arr, N + 1));   /* { dg-warning "argument 2 declared attribute .nonstring. is smaller than the specified bound 5" } */
+  T (strncmp (arr, str, N + 1));   /* { dg-warning "argument 1 declared attribute .nonstring. is smaller than the specified bound 5" } */
+
+  T (strncmp (str, parr, N + 1));
+  T (strncmp (parr, str, N + 1));
+
+  T (strncmp (p->str, p->arr, N));
+  T (strncmp (p->arr, p->str, N));
+  T (strncmp (p->parr, p->str, N));
+
+  T (strncmp (p->str, p->arr, N));
+  T (strncmp (p->arr, p->str, N));
+  T (strncmp (p->parr, p->str, N));
+}
+
+
+void test_strncmp_nowarn (struct MemArrays *p, size_t n)
+{
+  T (strncmp (str, str, n));
+  T (strncmp (str, arr, n));
+  T (strncmp (arr, str, n));
+
+  T (strncmp (str, ptr, n));
+  T (strncmp (str, parr, n));
+  T (strncmp (parr, str, n));
+
+  T (strncmp (p->str, p->arr, n));
+  T (strncmp (p->arr, p->str, n));
+  T (strncmp (p->parr, p->str, n));
+}
+
+
+void test_stpcpy (struct MemArrays *p)
+{
+  T (stpcpy (str, str));
+  T (stpcpy (str, arr));          /* { dg-warning "argument 2 declared attribute .nonstring." } */
+  T (stpcpy (arr, str));
+
+  T (stpcpy (str, ptr));
+  T (stpcpy (str, parr));         /* { dg-warning "argument 2 declared attribute .nonstring." } */
+  T (stpcpy (parr, str));
+
+  T (stpcpy (p->str, p->arr));    /* { dg-warning "argument 2 declared attribute .nonstring." } */
+  T (stpcpy (p->arr, p->str));
+  T (stpcpy (p->parr, p->str));
+}
+
+
+void test_stpncpy_nowarn (struct MemArrays *p, unsigned n)
+{
+  T (stpncpy (str, str, n));
+  T (stpncpy (str, arr, n));
+  T (stpncpy (arr, str, n));
+
+  T (stpncpy (str, ptr, n));
+  T (stpncpy (str, parr, n));
+  T (stpncpy (parr, str, n));
+
+  T (stpncpy (p->str, p->arr, n));
+  T (stpncpy (p->arr, p->str, n));
+  T (stpncpy (p->parr, p->str, n));
+}
+
+
+void test_stpncpy_warn (struct MemArrays *p, unsigned n)
+{
+  enum { N = sizeof arr };
+
+  T (stpncpy (str, str, N));
+  T (stpncpy (str, arr, N));
+  T (stpncpy (arr, str, N));
+
+  T (stpncpy (str, ptr, N));
+  T (stpncpy (str, parr, N));
+  T (stpncpy (parr, str, N));
+
+  T (stpncpy (p->str, p->arr, N));
+  T (stpncpy (p->arr, p->str, N));
+  T (stpncpy (p->parr, p->str, N));
+
+  T (stpncpy (ptr, str, N + 1));
+  T (stpncpy (ptr, arr, N + 1));          /* { dg-warning "argument 2 declared attribute .nonstring. is smaller than the specified bound 5" } */
+  T (stpncpy (arr, str, N + 1));          /* { dg-warning "writing 5 bytes into a region of size 4 overflows " } */
+
+  T (stpncpy (ptr, ptr, N + 1));
+  T (stpncpy (ptr, parr, N + 1));
+  T (stpncpy (parr, str, N + 1));
+
+  T (stpncpy (ptr, p->arr, N + 1));       /* { dg-warning "argument 2 declared attribute .nonstring. is smaller" } */
+  T (stpncpy (p->arr, p->str, N + 1));    /* { dg-warning "writing 5 bytes into a region of size 4 overflows" } */
+  T (stpncpy (p->parr, p->str, N + 1));
+}
+
+
+void test_strcat (struct MemArrays *p)
+{
+  T (strcat (str, str));
+  T (strcat (str, arr));          /* { dg-warning "argument 2 declared attribute .nonstring." } */
+  T (strcat (arr, str));
+
+  T (strcat (str, ptr));
+  T (strcat (str, parr));         /* { dg-warning "argument 2 declared attribute .nonstring." } */
+  T (strcat (parr, str));
+
+  T (strcat (p->str, p->arr));    /* { dg-warning "argument 2 declared attribute .nonstring." } */
+  T (strcat (p->arr, p->str));
+  T (strcat (p->parr, p->str));
+}
+
+
+void test_strncat (struct MemArrays *p, unsigned n)
+{
+  T (strncat (str, str, n));
+  T (strncat (str, arr, n));      /* { dg-warning "argument 2 declared attribute .nonstring." } */
+  T (strncat (arr, str, n));
+
+  T (strncat (str, ptr, n));
+  T (strncat (str, parr, n));     /* { dg-warning "argument 2 declared attribute .nonstring." } */
+  T (strncat (parr, str, n));
+
+  T (strncat (p->str, p->arr, n));   /* { dg-warning "argument 2 declared attribute .nonstring." } */
+  T (strncat (p->arr, p->str, n));
+  T (strncat (p->parr, p->str, n));
+}
+
+
+void test_strcpy (struct MemArrays *p)
+{
+  T (strcpy (str, str));
+  T (strcpy (str, arr));          /* { dg-warning "argument 2 declared attribute .nonstring." } */
+  T (strcpy (arr, str));
+
+  T (strcpy (str, ptr));
+  T (strcpy (str, parr));         /* { dg-warning "argument 2 declared attribute .nonstring." } */
+  T (strcpy (parr, str));
+
+  T (strcpy (p->str, p->arr));    /* { dg-warning "argument 2 declared attribute .nonstring." } */
+  T (strcpy (p->arr, p->str));
+  T (strcpy (p->parr, p->str));
+}
+
+
+void test_strncpy (struct MemArrays *p, unsigned n)
+{
+  T (strncpy (str, str, n));
+  T (strncpy (str, arr, n));
+  T (strncpy (arr, str, n));
+
+  T (strncpy (str, ptr, n));
+  T (strncpy (str, parr, n));
+  T (strncpy (parr, str, n));
+
+  T (strncpy (p->str, p->arr, n));
+  T (strncpy (p->arr, p->str, n));
+  T (strncpy (p->parr, p->str, n));
+}
+
+
+void test_strchr (struct MemArrays *p, int c)
+{
+  T (strchr (str, c));
+  T (strchr (arr, c));          /* { dg-warning "argument 1 declared attribute .nonstring." } */
+
+  T (strchr (ptr, c));
+  T (strchr (parr, c));         /* { dg-warning "argument 1 declared attribute .nonstring." } */
+
+  T (strchr (p->str, c));
+  T (strchr (p->arr, c));       /* { dg-warning "argument 1 declared attribute .nonstring." } */
+}
+
+
+void test_strdup (struct MemArrays *p)
+{
+  T (strdup (str));
+  T (strdup (arr));             /* { dg-warning "argument 1 declared attribute .nonstring." } */
+
+  T (strdup (ptr));
+  T (strdup (parr));            /* { dg-warning "argument 1 declared attribute .nonstring." } */
+
+  T (strdup (p->str));
+  T (strdup (p->arr));          /* { dg-warning "argument 1 declared attribute .nonstring." } */
+}
+
+
+void test_stnrdup_nowarn (struct MemArrays *p, size_t n)
+{
+  T (strndup (str, n));
+  T (strndup (arr, n));
+
+  T (strndup (ptr, n));
+  T (strndup (parr, n));
+
+  T (strndup (p->str, n));
+  T (strndup (p->arr, n));
+}
+
+
+void test_stnrdup_warn (struct MemArrays *p)
+{
+  enum { N = sizeof arr };
+
+  T (strndup (str, N));
+  T (strndup (arr, N));
+
+  T (strndup (ptr, N));
+  T (strndup (parr, N));
+
+  T (strndup (p->str, N));
+  T (strndup (p->arr, N));
+
+
+  T (strndup (arr, N + 1));     /* { dg-warning "argument 1 declared attribute .nonstring. is smaller than the specified bound 5" } */
+  T (strndup (parr, N + 1));
+  T (strndup (p->arr, N + 1));  /* { dg-warning "argument 1 declared attribute .nonstring. is smaller than the specified bound 5" } */
+  T (strndup (p->parr, N + 1));
+}
+
+
+void test_strlen (struct MemArrays *p, char *s NONSTRING, size_t n)
+{
+  T (strlen (str));
+  T (strlen (arr));             /* { dg-warning "argument 1 declared attribute .nonstring." } */
+
+  T (strlen (ptr));
+  T (strlen (parr));            /* { dg-warning "argument 1 declared attribute .nonstring." } */
+
+  T (strlen (p->str));
+  T (strlen (p->arr));          /* { dg-warning "argument 1 declared attribute .nonstring." } */
+
+  T (strlen (s));               /* { dg-warning "argument 1 declared attribute .nonstring." } */
+  {
+    strcpy (s, "123");
+    T (strlen (s));
+  }
+
+  {
+    char a[] __attribute__ ((nonstring)) = { 1, 2, 3 };
+
+    T (strlen (a));             /* { dg-warning "argument 1 declared attribute .nonstring." } */
+  }
+
+  {
+    char a[] __attribute__ ((nonstring)) = { 1, 2, 3, 4 };
+
+    strcpy (a, "12");
+    T (strlen (a));
+  }
+
+  {
+    char *p __attribute__ ((nonstring));
+    p = (char *)__builtin_malloc (n);
+    __builtin_memset (p, '*', n);
+
+    T (strlen (p));             /* { dg-warning "argument 1 declared attribute .nonstring." } */
+
+    strcpy (p, "12345");
+    T (strlen (p));
+  }
+}
+
+
+void test_strnlen (struct MemArrays *p, size_t n)
+{
+  T (strnlen (str, n));
+  T (strnlen (arr, n));
+
+  T (strnlen (ptr, n));
+  T (strnlen (parr, n));
+
+  T (strnlen (p->str, n));
+  T (strnlen (p->arr, n));
+}
+
+
+/* Verify no warnings are issued for raw mempory functions.  */
+
+void test_mem_functions (struct MemArrays *p, int c, size_t n)
+{
+  T (memchr (arr, c, n));
+  T (memchr (parr, c, n));
+  T (memchr (p->arr, c, n));
+  T (memchr (p->parr, c, n));
+
+  T (memcmp (str, arr, n));
+  T (memcmp (arr, str, n));
+  T (memcmp (str, parr, n));
+  T (memcmp (parr, str, n));
+  T (memcmp (p->str, p->arr, n));
+  T (memcmp (p->arr, p->str, n));
+  T (memcmp (p->parr, p->str, n));
+
+  T (memcpy (str, arr, n));
+  T (memcpy (arr, str, n));
+  T (memcpy (str, parr, n));
+  T (memcpy (parr, str, n));
+  T (memcpy (p->str, p->arr, n));
+  T (memcpy (p->arr, p->str, n));
+  T (memcpy (p->parr, p->str, n));
+
+  T (memmove (str, arr, n));
+  T (memmove (arr, str, n));
+  T (memmove (str, parr, n));
+  T (memmove (parr, str, n));
+  T (memmove (p->str, p->arr, n));
+  T (memmove (p->arr, p->str, n));
+  T (memmove (p->parr, p->str, n));
+}
index ff32301..48b9241 100644 (file)
@@ -51,6 +51,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic.h"
 #include "intl.h"
 #include "attribs.h"
+#include "calls.h"
 
 /* A vector indexed by SSA_NAME_VERSION.  0 means unknown, positive value
    is an index into strinfo vector, negative value stands for
@@ -152,8 +153,11 @@ struct decl_stridxlist_map
    mappings.  */
 static hash_map<tree_decl_hash, stridxlist> *decl_to_stridxlist_htab;
 
+/* Hash table mapping strlen calls to stridx instances describing
+   the calls' arguments.  Non-null only when warn_stringop_truncation
+   is non-zero.  */
 typedef std::pair<int, location_t> stridx_strlenloc;
-static hash_map<tree, stridx_strlenloc> strlen_to_stridx;
+static hash_map<tree, stridx_strlenloc> *strlen_to_stridx;
 
 /* Obstack for struct stridxlist and struct decl_stridxlist_map.  */
 static struct obstack stridx_obstack;
@@ -1207,8 +1211,11 @@ handle_builtin_strlen (gimple_stmt_iterator *gsi)
              gcc_assert (si->full_string_p);
            }
 
-         location_t loc = gimple_location (stmt);
-         strlen_to_stridx.put (lhs, stridx_strlenloc (idx, loc));
+         if (strlen_to_stridx)
+           {
+             location_t loc = gimple_location (stmt);
+             strlen_to_stridx->put (lhs, stridx_strlenloc (idx, loc));
+           }
          return;
        }
     }
@@ -1253,8 +1260,11 @@ handle_builtin_strlen (gimple_stmt_iterator *gsi)
       set_strinfo (idx, si);
       find_equal_ptrs (src, idx);
 
-      location_t loc = gimple_location (stmt);
-      strlen_to_stridx.put (lhs, stridx_strlenloc (idx, loc));
+      if (strlen_to_stridx)
+       {
+         location_t loc = gimple_location (stmt);
+         strlen_to_stridx->put (lhs, stridx_strlenloc (idx, loc));
+       }
     }
 }
 
@@ -1691,9 +1701,6 @@ is_strlen_related_p (tree src, tree len)
 static bool
 maybe_diag_stxncpy_trunc (gimple_stmt_iterator gsi, tree src, tree cnt)
 {
-  if (!warn_stringop_truncation)
-    return false;
-
   gimple *stmt = gsi_stmt (gsi);
 
   wide_int cntrange[2];
@@ -1733,35 +1740,15 @@ maybe_diag_stxncpy_trunc (gimple_stmt_iterator gsi, tree src, tree cnt)
     return false;
 
   tree dst = gimple_call_arg (stmt, 0);
-
-  /* See if the destination is declared with attribute "nonstring"
-     and if so, avoid the truncation warning.  */
-  if (TREE_CODE (dst) == SSA_NAME)
-    {
-      if (SSA_NAME_IS_DEFAULT_DEF (dst))
-       dst = SSA_NAME_VAR (dst);
-      else
-       {
-         gimple *def = SSA_NAME_DEF_STMT (dst);
-
-         if (is_gimple_assign (def)
-             && gimple_assign_rhs_code (def) == ADDR_EXPR)
-           dst = gimple_assign_rhs1 (def);
-       }
-    }
-
   tree dstdecl = dst;
   if (TREE_CODE (dstdecl) == ADDR_EXPR)
     dstdecl = TREE_OPERAND (dstdecl, 0);
 
-  {
-    tree d = dstdecl;
-    if (TREE_CODE (d) == COMPONENT_REF)
-      d = TREE_OPERAND (d, 1);
-
-    if (DECL_P (d) && lookup_attribute ("nonstring", DECL_ATTRIBUTES (d)))
-      return false;
-  }
+  /* If the destination refers to a an array/pointer declared nonstring
+     return early.  */
+  tree ref = NULL_TREE;
+  if (get_attr_nonstring_decl (dstdecl, &ref))
+    return false;
 
   /* Look for dst[i] = '\0'; after the stxncpy() call and if found
      avoid the truncation warning.  */
@@ -1770,12 +1757,32 @@ maybe_diag_stxncpy_trunc (gimple_stmt_iterator gsi, tree src, tree cnt)
 
   if (!gsi_end_p (gsi) && is_gimple_assign (next_stmt))
     {
-      HOST_WIDE_INT off;
-      dstdecl = get_addr_base_and_unit_offset (dstdecl, &off);
-
       tree lhs = gimple_assign_lhs (next_stmt);
-      tree lhsbase = get_addr_base_and_unit_offset (lhs, &off);
-      if (lhsbase && operand_equal_p (dstdecl, lhsbase, 0))
+      tree_code code = TREE_CODE (lhs);
+      if (code == ARRAY_REF || code == MEM_REF)
+       lhs = TREE_OPERAND (lhs, 0);
+
+      tree func = gimple_call_fndecl (stmt);
+      if (DECL_FUNCTION_CODE (func) == BUILT_IN_STPNCPY)
+       {
+         tree ret = gimple_call_lhs (stmt);
+         if (ret && operand_equal_p (ret, lhs, 0))
+           return false;
+       }
+
+      /* Determine the base address and offset of the reference,
+        ignoring the innermost array index.  */
+      if (TREE_CODE (ref) == ARRAY_REF)
+       ref = TREE_OPERAND (ref, 0);
+
+      HOST_WIDE_INT dstoff;
+      tree dstbase = get_addr_base_and_unit_offset (ref, &dstoff);
+
+      HOST_WIDE_INT lhsoff;
+      tree lhsbase = get_addr_base_and_unit_offset (lhs, &lhsoff);
+      if (lhsbase
+         && dstoff == lhsoff
+         && operand_equal_p (dstbase, lhsbase, 0))
        return false;
     }
 
@@ -1909,6 +1916,9 @@ maybe_diag_stxncpy_trunc (gimple_stmt_iterator gsi, tree src, tree cnt)
 static void
 handle_builtin_stxncpy (built_in_function, gimple_stmt_iterator *gsi)
 {
+  if (!strlen_to_stridx)
+    return;
+
   gimple *stmt = gsi_stmt (*gsi);
 
   bool with_bounds = gimple_call_with_bounds_p (stmt);
@@ -1919,7 +1929,7 @@ handle_builtin_stxncpy (built_in_function, gimple_stmt_iterator *gsi)
   /* If the length argument was computed from strlen(S) for some string
      S retrieve the strinfo index for the string (PSS->FIRST) alonng with
      the location of the strlen() call (PSS->SECOND).  */
-  stridx_strlenloc *pss = strlen_to_stridx.get (len);
+  stridx_strlenloc *pss = strlen_to_stridx->get (len);
   if (!pss || pss->first <= 0)
     {
       if (maybe_diag_stxncpy_trunc (*gsi, src, len))
@@ -1953,13 +1963,13 @@ handle_builtin_stxncpy (built_in_function, gimple_stmt_iterator *gsi)
      whether its value is known.  Otherwise, issue the more generic
      -Wstringop-overflow which triggers for LEN arguments that in
      any meaningful way depend on strlen(SRC).  */
-  if (warn_stringop_truncation
-      && sisrc == silen
-      && is_strlen_related_p (src, len))
-    warned = warning_at (callloc, OPT_Wstringop_truncation,
-                        "%qD output truncated before terminating nul "
-                        "copying as many bytes from a string as its length",
-                        func);
+  if (sisrc == silen
+      && is_strlen_related_p (src, len)
+      && warning_at (callloc, OPT_Wstringop_truncation,
+                    "%qD output truncated before terminating nul "
+                    "copying as many bytes from a string as its length",
+                    func))
+    warned = true;
   else if (silen && is_strlen_related_p (src, silen->ptr))
     warned = warning_at (callloc, OPT_Wstringop_overflow_,
                         "%qD specified bound depends on the length "
@@ -2966,9 +2976,12 @@ strlen_optimize_stmt (gimple_stmt_iterator *gsi)
          fold_strstr_to_strncmp (gimple_assign_rhs1 (stmt),
                                  gimple_assign_rhs2 (stmt), stmt);
 
-       tree rhs1 = gimple_assign_rhs1 (stmt);
-       if (stridx_strlenloc *ps = strlen_to_stridx.get (rhs1))
-         strlen_to_stridx.put (lhs, stridx_strlenloc (*ps));
+       if (strlen_to_stridx)
+         {
+           tree rhs1 = gimple_assign_rhs1 (stmt);
+           if (stridx_strlenloc *ps = strlen_to_stridx->get (rhs1))
+             strlen_to_stridx->put (lhs, stridx_strlenloc (*ps));
+         }
       }
     else if (TREE_CODE (lhs) != SSA_NAME && !TREE_SIDE_EFFECTS (lhs))
        {
@@ -3199,6 +3212,10 @@ public:
 unsigned int
 pass_strlen::execute (function *fun)
 {
+  gcc_assert (!strlen_to_stridx);
+  if (warn_stringop_overflow || warn_stringop_truncation)
+    strlen_to_stridx = new hash_map<tree, stridx_strlenloc> ();
+
   ssa_ver_to_stridx.safe_grow_cleared (num_ssa_names);
   max_stridx = 1;
 
@@ -3220,7 +3237,12 @@ pass_strlen::execute (function *fun)
   laststmt.len = NULL_TREE;
   laststmt.stridx = 0;
 
-  strlen_to_stridx.empty ();
+  if (strlen_to_stridx)
+    {
+      strlen_to_stridx->empty ();
+      delete strlen_to_stridx;
+      strlen_to_stridx = NULL;
+    }
 
   return 0;
 }