Hi!

As requested in the PR117178 thread, the following patch allows nonstring
attribute also on multi-dimensional arrays (with cv char/signed char/unsigned
char as innermost element type) and pointers to such multi-dimensional arrays
or pointers to single-dimensional cv char/signed char/unsigned char arrays.
Given that (unfortunately) nonstring is a decl attribute rather than type
attribute, I think restricting it to single-dimensional arrays makes no
sense, even multi-dimensional ones can be used for storage of non-nul
terminated strings.

Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?
Or defer for GCC 16?

I really don't know what the kernel plans are, whether
they'll go with -Wno-unterminated-string-initialization added in Makefiles,
or whether the plan is to use nonstring attributes to quiet the warning.
In the latter case, some of the nonstring attributes will need to be
conditional on gcc version, because gcc before this patch will reject it
on multidimensional arrays.

2025-02-14  Jakub Jelinek  <ja...@redhat.com>

        PR c/117178
gcc/
        * tree.cc (get_attr_nonstring_decl): Look through all ARRAY_REFs, not
        just one and handle COMPONENT_REF and MEM_REF after skipping those
        rather than only when there wasn't ARRAY_REF.  Formatting fix.
gcc/c-family/
        * c-attribs.cc (handle_nonstring_attribute): Allow the attribute also
        on multi-dimensional arrays with char/signed char/unsigned char
        element type or pointers to such single and multi-dimensional arrays.
gcc/testsuite/
        * c-c++-common/attr-nonstring-7.c: Remove one xfail.
        * c-c++-common/attr-nonstring-9.c: New test.
        * c-c++-common/attr-nonstring-10.c: New test.
        * c-c++-common/attr-nonstring-11.c: New test.
        * c-c++-common/attr-nonstring-12.c: New test.
        * c-c++-common/attr-nonstring-13.c: New test.
        * c-c++-common/attr-nonstring-14.c: New test.
        * c-c++-common/attr-nonstring-15.c: New test.
        * c-c++-common/attr-nonstring-16.c: New test.

--- gcc/tree.cc.jj      2025-02-13 14:14:44.330394074 +0100
+++ gcc/tree.cc 2025-02-13 14:47:49.383764368 +0100
@@ -15307,15 +15307,17 @@ get_attr_nonstring_decl (tree expr, tree
      DECL.  */
   if (var)
     decl = var;
-  else if (TREE_CODE (decl) == ARRAY_REF)
-    decl = TREE_OPERAND (decl, 0);
-  else if (TREE_CODE (decl) == COMPONENT_REF)
-    decl = TREE_OPERAND (decl, 1);
-  else if (TREE_CODE (decl) == MEM_REF)
-    return get_attr_nonstring_decl (TREE_OPERAND (decl, 0), ref);
+  else
+    {
+      while (TREE_CODE (decl) == ARRAY_REF)
+       decl = TREE_OPERAND (decl, 0);
+      if (TREE_CODE (decl) == COMPONENT_REF)
+       decl = TREE_OPERAND (decl, 1);
+      else if (TREE_CODE (decl) == MEM_REF)
+       return get_attr_nonstring_decl (TREE_OPERAND (decl, 0), ref);
+    }
 
-  if (DECL_P (decl)
-      && lookup_attribute ("nonstring", DECL_ATTRIBUTES (decl)))
+  if (DECL_P (decl) && lookup_attribute ("nonstring", DECL_ATTRIBUTES (decl)))
     return decl;
 
   return NULL_TREE;
--- gcc/c-family/c-attribs.cc.jj        2025-01-17 11:29:33.117696690 +0100
+++ gcc/c-family/c-attribs.cc   2025-02-13 15:00:44.613027064 +0100
@@ -5117,8 +5117,9 @@ handle_nonstring_attribute (tree *node,
       if (POINTER_TYPE_P (type) || TREE_CODE (type) == ARRAY_TYPE)
        {
          /* Accept the attribute on arrays and pointers to all three
-            narrow character types.  */
-         tree eltype = TREE_TYPE (type);
+            narrow character types, including multi-dimensional arrays
+            or pointers to them.  */
+         tree eltype = strip_array_types (TREE_TYPE (type));
          eltype = TYPE_MAIN_VARIANT (eltype);
          if (eltype == char_type_node
              || eltype == signed_char_type_node
--- gcc/testsuite/c-c++-common/attr-nonstring-7.c.jj    2020-01-12 
11:54:37.000404553 +0100
+++ gcc/testsuite/c-c++-common/attr-nonstring-7.c       2025-02-13 
16:18:02.961896495 +0100
@@ -56,7 +56,7 @@ test_memarray_cstidx_idx (const char *s)
   /* The destination below is represented as &MEM[(void *)&a + 20B] and
      which (in general) doesn't make it possible to determine what member
      it refers to.  */
-  strncpy (p, s, sizeof b[1].a - 4);   /* { dg-bogus "-Wstringop-truncation" 
"" { xfail *-*-*} } */
+  strncpy (p, s, sizeof b[1].a - 4);   /* { dg-bogus "-Wstringop-truncation" } 
*/
   return b;
 }
 
--- gcc/testsuite/c-c++-common/attr-nonstring-9.c.jj    2025-02-13 
16:32:38.375848629 +0100
+++ gcc/testsuite/c-c++-common/attr-nonstring-9.c       2025-02-14 
10:58:08.557320758 +0100
@@ -0,0 +1,51 @@
+/* Test to exercise attribute "nonstring" syntax.
+   { dg-do compile }
+   { dg-options "-Wattributes" }  */
+
+#define ATTR(list) __attribute__ (list)
+#define NONSTR     ATTR ((nonstring))
+
+/* Verify it's accepted on char[] arrays.  */
+extern NONSTR char nsx_1[][3];
+extern char NONSTR nsx_2[][3];
+extern char nsx_3[][3] NONSTR;
+
+extern NONSTR char ns1[1][4];
+extern char NONSTR ns3[3][5];
+extern char ns5[5][6] NONSTR;
+
+extern NONSTR char ns4[1][2][3][4];
+extern char NONSTR ns5[2][3][4][5][6];
+extern char ns6[1][2][3][1][2][3][1][2][3];
+
+/* Verify it's accepted on char[] pointers.  */
+extern NONSTR char (*pns_1)[3];
+extern char NONSTR (*pns_2)[4];
+extern char (*NONSTR pns_3)[5];
+
+extern NONSTR char (*pns_4)[1][2];
+extern char NONSTR (*pns_5)[2][3][1][7][4];
+extern char (*NONSTR pns_6)[1][1][1][2][1][1][1][2][1][1][1][2][1][1][7];
+
+struct S
+{
+/* Verify it's accepted on char[] member pointers.  */
+  NONSTR char (*mpns_1)[3];
+  char NONSTR (*mpns_2)[4];
+  char (*NONSTR mpns_3)[5];
+
+/* Verify it's accepted on char[] member arrays.  */
+  NONSTR char mns1[1][2];
+  char NONSTR mns3[3][3];
+  char mns5[5][4] NONSTR;
+
+/* Verify it's accepted on char[] flexible array members.  */
+  char mnsx[][5] NONSTR;
+};
+
+void func (NONSTR char (*pns1)[2], char NONSTR (*pns2)[3], char (* NONSTR 
pns3)[4])
+{
+  (void)pns1;
+  (void)pns2;
+  (void)pns3;
+}
--- gcc/testsuite/c-c++-common/attr-nonstring-10.c.jj   2025-02-14 
10:30:27.474616975 +0100
+++ gcc/testsuite/c-c++-common/attr-nonstring-10.c      2025-02-14 
10:38:04.138188110 +0100
@@ -0,0 +1,75 @@
+/* Test to exercise attribute "nonstring".
+   { dg-do compile }
+   { dg-options "-O2 -Wattributes -Wstringop-truncation 
-ftrack-macro-expansion=0" }  */
+
+#define ATTR(list) __attribute__ (list)
+#define NONSTR     ATTR ((nonstring))
+#define strncpy(d, s, n) (__builtin_strncpy ((d), (s), (n)), sink (d))
+
+void sink (void*);
+
+/* Global string with an known bound.  */
+extern char gns3[][3] NONSTR;
+
+/* Global non-string pointers.  */
+extern NONSTR char (*pns_1)[1];
+extern char (* NONSTR pns_2)[2];
+extern char (*pns_3)[3] NONSTR;
+
+struct MemArrays
+{
+  NONSTR char ma3[2][3];
+  char NONSTR ma4[3][4];
+  char ma5[4][5] NONSTR;
+};
+
+
+void test_array (const char *s, unsigned n)
+{
+  const char s7[] = "1234567";
+
+  strncpy (gns3[2], "", 0);           /* { dg-warning "destination unchanged 
after copying no bytes" } */
+  strncpy (gns3[2], "a", 1);          /* { dg-warning "output truncated before 
terminating nul copying 1 byte from a string of the same length" } */
+  strncpy (gns3[2], "a", 2);
+  strncpy (gns3[2], "a", 3);
+  strncpy (gns3[2], "ab", 3);
+  strncpy (gns3[2], "abc", 3);        /* { dg-warning "output truncated before 
terminating nul copying 3 bytes from a string of the same length" } */
+}
+
+
+void test_pointer (const char *s, unsigned n)
+{
+  const char s7[] = "1234567";
+
+  strncpy (*pns_1, "a", 1);
+  strncpy (*pns_2, "ab", 2);
+  strncpy (*pns_3, "abc", 3);
+  strncpy (*pns_3, s7, 3);
+
+  strncpy (*pns_1, s, 1);
+  strncpy (*pns_2, s, 1);
+  strncpy (*pns_3, s, 1);
+
+  strncpy (*pns_1, s, n);
+  strncpy (*pns_2, s, n);
+  strncpy (*pns_3, s, n);
+}
+
+
+void test_member_array (struct MemArrays *p, const char *s, unsigned n)
+{
+  const char s7[] = "1234567";
+
+  strncpy (p->ma3[1], "", 0);
+  strncpy (p->ma3[1], "a", 1);
+  strncpy (p->ma4[2], "ab", 2);
+  strncpy (p->ma5[3], "abc", 3);
+
+  strncpy (p->ma3[1], s, 1);
+  strncpy (p->ma4[2], s, 1);
+  strncpy (p->ma5[3], s, 1);
+
+  strncpy (p->ma3[1], s7, n);
+  strncpy (p->ma4[2], s7, n);
+  strncpy (p->ma5[3], s7, n);
+}
--- gcc/testsuite/c-c++-common/attr-nonstring-11.c.jj   2025-02-14 
10:38:58.190427164 +0100
+++ gcc/testsuite/c-c++-common/attr-nonstring-11.c      2025-02-14 
11:08:06.355937446 +0100
@@ -0,0 +1,466 @@
+/* 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))
+
+/* STR needs to be bigger than ARR to trigger warnings, otherwise
+   since STR must be a string, using both in a string function
+   can be assumed to be safe even if ARR isn't nul-terminated.  */
+char str[3][5];
+char arr[3][4] NONSTRING;
+
+char (*ptr)[6];
+char (*parr)[6] NONSTRING;
+
+struct MemArrays
+{
+  char str[5][5];
+  char arr[4][4] NONSTRING;
+  char (*parr)[4] NONSTRING;
+};
+
+void sink (int, ...);
+
+
+#define T(call)  sink (0, call)
+
+void test_printf (struct MemArrays *p)
+{
+  T (printf (str[2]));
+  T (printf (arr[2]));             /* { dg-warning "argument 1 declared 
attribute .nonstring." } */
+
+  T (printf (*ptr));
+  T (printf (*parr));            /* { dg-warning "argument 1 declared 
attribute .nonstring." } */
+
+  T (printf (p->str[2]));
+  T (printf (p->arr[2]));          /* { dg-warning "argument 1 declared 
attribute .nonstring." } */
+}
+
+
+void test_puts (struct MemArrays *p)
+{
+  T (puts (str[2]));
+  T (puts (arr[2]));               /* { dg-warning "argument 1 declared 
attribute .nonstring." } */
+
+  T (puts (*ptr));
+  T (puts (*parr));              /* { dg-warning "argument 1 declared 
attribute .nonstring." } */
+
+  T (puts (p->str[2]));
+  T (puts (p->arr[2]));            /* { dg-warning "argument 1 declared 
attribute .nonstring." } */
+}
+
+
+void test_snprintf (char *d, size_t n, struct MemArrays *p)
+{
+  T (snprintf (d, n, str[2]));
+  T (snprintf (d, n, arr[2]));       /* { 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[2]));
+  T (snprintf (d, n, p->arr[2]));    /* { dg-warning "argument 3 declared 
attribute .nonstring." } */
+}
+
+
+void test_sprintf (char *d, struct MemArrays *p)
+{
+  T (sprintf (d, str[2]));
+  T (sprintf (d, arr[2]));           /* { 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[2]));
+  T (sprintf (d, p->arr[2]));        /* { 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[2], va));
+  T (vsnprintf (d, n, arr[2], 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[2], va));
+  T (vsnprintf (d, n, p->arr[2], va)); /* { dg-warning "argument 3 declared 
attribute .nonstring." } */
+}
+
+
+void test_vsprintf (char *d, struct MemArrays *p, va_list va)
+{
+  T (vsprintf (d, str[2], va));
+  T (vsprintf (d, arr[2], 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[2], va));
+  T (vsprintf (d, p->arr[2], va));   /* { dg-warning "argument 2 declared 
attribute .nonstring." } */
+}
+
+
+void test_strcmp (struct MemArrays *p)
+{
+  T (strcmp (str[2], str[2]));
+  T (strcmp (str[2], arr[2]));          /* { dg-warning "argument 2 declared 
attribute .nonstring." } */
+  T (strcmp (arr[2], str[2]));          /* { dg-warning "argument 1 declared 
attribute .nonstring." } */
+
+  T (strcmp (str[2], *ptr));
+  T (strcmp (str[2], *parr));         /* { dg-warning "argument 2 declared 
attribute .nonstring." } */
+  T (strcmp (*parr, str[2]));         /* { dg-warning "argument 1 declared 
attribute .nonstring." } */
+
+  T (strcmp (p->str[2], p->arr[2]));    /* { dg-warning "argument 2 declared 
attribute .nonstring." } */
+  T (strcmp (p->arr[2], p->str[2]));    /* { dg-warning "argument 1 declared 
attribute .nonstring." } */
+  T (strcmp (*p->parr, p->str[2]));   /* { dg-warning "argument 1 declared 
attribute .nonstring." } */
+}
+
+
+void test_strncmp_warn (struct MemArrays *p)
+{
+  enum { N = sizeof arr[2] };
+  T (strncmp (str[2], arr[2], N));
+  T (strncmp (arr[2], str[2], N));
+
+  T (strncmp (str[2], arr[2], N + 1));   /* { dg-warning "argument 2 declared 
attribute .nonstring. is smaller than the specified bound 5" } */
+  T (strncmp (arr[2], str[2], N + 1));   /* { dg-warning "argument 1 declared 
attribute .nonstring. is smaller than the specified bound 5" } */
+
+  T (strncmp (str[2], *parr, N + 1));
+  T (strncmp (*parr, str[2], N + 1));
+
+  T (strncmp (p->str[2], p->arr[2], N));
+  T (strncmp (p->arr[2], p->str[2], N));
+  T (strncmp (*p->parr, p->str[2], N));
+
+  T (strncmp (p->str[2], p->arr[2], N));
+  T (strncmp (p->arr[2], p->str[2], N));
+  T (strncmp (*p->parr, p->str[2], N));
+}
+
+
+void test_strncmp_nowarn (struct MemArrays *p, size_t n)
+{
+  T (strncmp (str[2], str[2], n));
+  T (strncmp (str[2], arr[2], n));
+  T (strncmp (arr[2], str[2], n));
+
+  T (strncmp (str[2], *ptr, n));
+  T (strncmp (str[2], *parr, n));
+  T (strncmp (*parr, str[2], n));
+
+  T (strncmp (p->str[2], p->arr[2], n));
+  T (strncmp (p->arr[2], p->str[2], n));
+  T (strncmp (*p->parr, p->str[2], n));
+}
+
+
+void test_stpcpy (struct MemArrays *p)
+{
+  T (stpcpy (str[2], str[2]));
+  T (stpcpy (str[2], arr[2]));          /* { dg-warning "argument 2 declared 
attribute .nonstring." } */
+  T (stpcpy (arr[2], str[2]));
+
+  T (stpcpy (str[2], *ptr));
+  T (stpcpy (str[2], *parr));         /* { dg-warning "argument 2 declared 
attribute .nonstring." } */
+  T (stpcpy (*parr, str[2]));
+
+  T (stpcpy (p->str[2], p->arr[2]));    /* { dg-warning "argument 2 declared 
attribute .nonstring." } */
+  T (stpcpy (p->arr[2], p->str[2]));
+  T (stpcpy (*p->parr, p->str[2]));
+}
+
+
+void test_stpncpy_nowarn (struct MemArrays *p, unsigned n)
+{
+  T (stpncpy (str[2], str[2], n));
+  T (stpncpy (str[2], arr[2], n));
+  T (stpncpy (arr[2], str[2], n));
+
+  T (stpncpy (str[2], *ptr, n));
+  T (stpncpy (str[2], *parr, n));
+  T (stpncpy (*parr, str[2], n));
+
+  T (stpncpy (p->str[2], p->arr[2], n));
+  T (stpncpy (p->arr[2], p->str[2], n));
+  T (stpncpy (*p->parr, p->str[2], n));
+}
+
+
+void test_stpncpy_warn (struct MemArrays *p, unsigned n)
+{
+  enum { N = sizeof arr[2] };
+
+  T (stpncpy (str[2], str[2], N));
+  T (stpncpy (str[2], arr[2], N));
+  T (stpncpy (arr[2], str[2], N));
+
+  T (stpncpy (str[2], *ptr, N));
+  T (stpncpy (str[2], *parr, N));
+  T (stpncpy (*parr, str[2], N));
+
+  T (stpncpy (p->str[2], p->arr[2], N));
+  T (stpncpy (p->arr[2], p->str[2], N));
+  T (stpncpy (*p->parr, p->str[2], N));
+
+  T (stpncpy (*ptr, str[2], N + 1));
+  T (stpncpy (*ptr, arr[2], N + 1));          /* { dg-warning "argument 2 
declared attribute .nonstring. is smaller than the specified bound 5" } */
+  T (stpncpy (arr[2], str[2], 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[2], N + 1));
+
+  T (stpncpy (*ptr, p->arr[2], N + 1));       /* { dg-warning "argument 2 
declared attribute .nonstring. is smaller" } */
+  T (stpncpy (p->arr[2], p->str[2], N + 1));    /* { dg-warning "writing 5 
bytes into a region of size 4 overflows " } */
+  T (stpncpy (*p->parr, p->str[2], N + 1));
+}
+
+
+void test_strcat (struct MemArrays *p)
+{
+  T (strcat (str[2], str[2]));
+  T (strcat (str[2], arr[2]));          /* { dg-warning "argument 2 declared 
attribute .nonstring." } */
+  T (strcat (arr[2], str[2]));
+
+  T (strcat (str[2], *ptr));
+  T (strcat (str[2], *parr));         /* { dg-warning "argument 2 declared 
attribute .nonstring." } */
+  T (strcat (*parr, str[2]));
+
+  T (strcat (p->str[2], p->arr[2]));    /* { dg-warning "argument 2 declared 
attribute .nonstring." } */
+  T (strcat (p->arr[2], p->str[2]));
+  T (strcat (*p->parr, p->str[2]));
+}
+
+
+void test_strncat (struct MemArrays *p, unsigned n)
+{
+  T (strncat (str[2], str[2], n));
+  T (strncat (str[2], arr[2], n));      /* { dg-warning "argument 2 declared 
attribute .nonstring." } */
+  T (strncat (arr[2], str[2], n));
+
+  T (strncat (str[2], *ptr, n));
+  T (strncat (str[2], *parr, n));     /* { dg-warning "argument 2 declared 
attribute .nonstring." } */
+  T (strncat (*parr, str[2], n));
+
+  T (strncat (p->str[2], p->arr[2], n));   /* { dg-warning "argument 2 
declared attribute .nonstring." } */
+  T (strncat (p->arr[2], p->str[2], n));
+  T (strncat (*p->parr, p->str[2], n));
+}
+
+
+void test_strcpy (struct MemArrays *p)
+{
+  T (strcpy (str[2], str[2]));
+  T (strcpy (str[2], arr[2]));          /* { dg-warning "argument 2 declared 
attribute .nonstring." } */
+  T (strcpy (arr[2], str[2]));
+
+  T (strcpy (str[2], *ptr));
+  T (strcpy (str[2], *parr));         /* { dg-warning "argument 2 declared 
attribute .nonstring." } */
+  T (strcpy (*parr, str[2]));
+
+  T (strcpy (p->str[2], p->arr[2]));    /* { dg-warning "argument 2 declared 
attribute .nonstring." } */
+  T (strcpy (p->arr[2], p->str[2]));
+  T (strcpy (*p->parr, p->str[2]));
+}
+
+
+void test_strncpy (struct MemArrays *p, unsigned n)
+{
+  T (strncpy (str[2], str[2], n));
+  T (strncpy (str[2], arr[2], n));
+  T (strncpy (arr[2], str[2], n));
+
+  T (strncpy (str[2], *ptr, n));
+  T (strncpy (str[2], *parr, n));
+  T (strncpy (*parr, str[2], n));
+
+  T (strncpy (p->str[2], p->arr[2], n));
+  T (strncpy (p->arr[2], p->str[2], n));
+  T (strncpy (*p->parr, p->str[2], n));
+}
+
+
+void test_strchr (struct MemArrays *p, int c)
+{
+  T (strchr (str[2], c));
+  T (strchr (arr[2], 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[2], c));
+  T (strchr (p->arr[2], c));       /* { dg-warning "argument 1 declared 
attribute .nonstring." } */
+}
+
+
+void test_strdup (struct MemArrays *p)
+{
+  T (strdup (str[2]));
+  T (strdup (arr[2]));             /* { dg-warning "argument 1 declared 
attribute .nonstring." } */
+
+  T (strdup (*ptr));
+  T (strdup (*parr));            /* { dg-warning "argument 1 declared 
attribute .nonstring." } */
+
+  T (strdup (p->str[2]));
+  T (strdup (p->arr[2]));          /* { dg-warning "argument 1 declared 
attribute .nonstring." } */
+}
+
+
+void test_stnrdup_nowarn (struct MemArrays *p, size_t n)
+{
+  T (strndup (str[2], n));
+  T (strndup (arr[2], n));
+
+  T (strndup (*ptr, n));
+  T (strndup (*parr, n));
+
+  T (strndup (p->str[2], n));
+  T (strndup (p->arr[2], n));
+}
+
+
+void test_stnrdup_warn (struct MemArrays *p)
+{
+  enum { N = sizeof arr[2] };
+
+  T (strndup (str[2], N));
+  T (strndup (arr[2], N));
+
+  T (strndup (*ptr, N));
+  T (strndup (*parr, N));
+
+  T (strndup (p->str[2], N));
+  T (strndup (p->arr[2], N));
+
+
+  T (strndup (arr[2], N + 1));     /* { dg-warning "argument 1 declared 
attribute 'nonstring' is smaller than the specified bound 5|specified bound 5 
exceeds source size 4" } */
+  T (strndup (*parr, N + 1));
+  T (strndup (p->arr[2], N + 1));  /* { dg-warning "argument 1 declared 
attribute 'nonstring' is smaller than the specified bound 5|specified bound 5 
exceeds source size 4" } */
+  T (strndup (*p->parr, N + 1));
+}
+
+
+void test_strlen (struct MemArrays *p, char *s NONSTRING, size_t n)
+{
+  T (strlen (str[2]));
+  T (strlen (arr[2]));             /* { dg-warning "argument 1 declared 
attribute .nonstring." } */
+
+  T (strlen (*ptr));
+  T (strlen (*parr));            /* { dg-warning "argument 1 declared 
attribute .nonstring." } */
+
+  T (strlen (p->str[2]));
+  T (strlen (p->arr[2]));          /* { 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[][3] __attribute__ ((nonstring)) = { { 1, 2, 3 } };
+
+    T (strlen (a[0]));             /* { dg-warning "argument 1 declared 
attribute .nonstring." } */
+  }
+
+  {
+    char a[][4] __attribute__ ((nonstring)) = { { 1, 2, 3, 4 } };
+
+    strcpy (a[0], "12");
+    T (strlen (a[0]));
+  }
+}
+
+
+void test_strnlen (struct MemArrays *p, size_t n)
+{
+  T (strnlen (str[2], n));
+  T (strnlen (arr[2], n));
+
+  T (strnlen (*ptr, n));
+  T (strnlen (*parr, n));
+
+  T (strnlen (p->str[2], n));
+  T (strnlen (p->arr[2], 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[2], c, n));
+  T (memchr (*parr, c, n));
+  T (memchr (p->arr[2], c, n));
+  T (memchr (*p->parr, c, n));
+
+  T (memcmp (str[2], arr[2], n));
+  T (memcmp (arr[2], str[2], n));
+  T (memcmp (str[2], *parr, n));
+  T (memcmp (*parr, str[2], n));
+  T (memcmp (p->str[2], p->arr[2], n));
+  T (memcmp (p->arr[2], p->str[2], n));
+  T (memcmp (*p->parr, p->str[2], n));
+
+  T (memcpy (str[2], arr[2], n));
+  T (memcpy (arr[2], str[2], n));
+  T (memcpy (str[2], *parr, n));
+  T (memcpy (*parr, str[2], n));
+  T (memcpy (p->str[2], p->arr[2], n));
+  T (memcpy (p->arr[2], p->str[2], n));
+  T (memcpy (*p->parr, p->str[2], n));
+
+  T (memmove (str[2], arr[2], n));
+  T (memmove (arr[2], str[2], n));
+  T (memmove (str[2], *parr, n));
+  T (memmove (*parr, str[2], n));
+  T (memmove (p->str[2], p->arr[2], n));
+  T (memmove (p->arr[2], p->str[2], n));
+  T (memmove (*p->parr, p->str[2], n));
+}
--- gcc/testsuite/c-c++-common/attr-nonstring-12.c.jj   2025-02-14 
10:55:53.299214274 +0100
+++ gcc/testsuite/c-c++-common/attr-nonstring-12.c      2025-02-14 
10:59:41.936011251 +0100
@@ -0,0 +1,79 @@
+/* PR middle-end/83131 - c-c++/common/attr-nonstring-3 failure for strcmp
+   tests on PowerPC
+   { dg-do compile }
+   { dg-options "-O2 -Wstringop-overflow -ftrack-macro-expansion=0" }  */
+
+#if __cplusplus
+extern "C" {
+#endif
+
+typedef __SIZE_TYPE__ size_t;
+
+extern int strcmp (const char*, const char*);
+extern int strncmp (const char*, const char*, size_t);
+
+#if __cplusplus
+}   /* extern "C" */
+#endif
+
+extern char arx[][18] __attribute__ ((nonstring));
+extern char ar5[5][5] __attribute__ ((nonstring));
+extern char str[][18];
+
+enum { N = sizeof ar5[2] };
+enum { X = sizeof ar5[2] + 1 };
+
+
+int warn_strcmp_cst_1 (void)
+{
+  return strcmp ("bar", arx[3]);       /* { dg-warning "argument 2 declared 
attribute .nonstring." } */
+}
+
+int warn_strcmp_cst_2 (void)
+{
+  return strcmp (arx[3], "foo");       /* { dg-warning "argument 1 declared 
attribute .nonstring." } */
+}
+
+
+int warn_strncmp_cst_1 (void)
+{
+  return strncmp ("12345", ar5[2], X);   /* { dg-warning "argument 2 declared 
attribute .nonstring." } */
+}
+
+int warn_strncmp_cst_2 (void)
+{
+  return strncmp (ar5[2], "12345", X);   /* { dg-warning "argument 1 declared 
attribute .nonstring." } */
+}
+
+
+int nowarn_strncmp_cst_1 (void)
+{
+  return strncmp ("12345", ar5[2], N);
+}
+
+int nowarn_strncmp_cst_2 (void)
+{
+  return strncmp (ar5[2], "12345", N);
+}
+
+
+int warn_strncmp_var_1 (void)
+{
+  return strncmp (str[5], ar5[2], X);     /* { dg-warning "argument 2 declared 
attribute .nonstring." } */
+}
+
+int warn_strncmp_var_2 (void)
+{
+  return strncmp (ar5[2], str[5], X);     /* { dg-warning "argument 1 declared 
attribute .nonstring." } */
+}
+
+
+int nowarn_strncmp_var_1 (void)
+{
+  return strncmp (str[5], ar5[2], N);
+}
+
+int nowarn_strncmp_var_2 (void)
+{
+  return strncmp (ar5[2], str[5], N);
+}
--- gcc/testsuite/c-c++-common/attr-nonstring-13.c.jj   2025-02-14 
11:00:38.749214520 +0100
+++ gcc/testsuite/c-c++-common/attr-nonstring-13.c      2025-02-14 
11:05:48.589869427 +0100
@@ -0,0 +1,131 @@
+/* PR middle-end/84725 - enable attribute nonstring for all narrow character
+   types
+   Verify that using attribute nonstring with all three narrow character
+   types is accepted and using arrays and pointers to characters of all
+   three types (including their qualified forms) declared with the
+   attributes doesn't trigger -Wstringop-truncation warnings.
+   { dg-do compile }
+   { dg-options "-O -Wall -Wstringop-truncation" } */
+
+#if __cplusplus
+extern "C"
+#endif
+char* strncpy (char*, const char*, __SIZE_TYPE__);
+
+#define NONSTR __attribute__ ((nonstring))
+
+#define S "1234"
+
+struct Arrays
+{
+  char NONSTR a[4][4];
+  signed char NONSTR b[4][4];
+  unsigned char NONSTR c[4][4];
+};
+
+void test_arrays (struct Arrays *p, const char *s)
+{
+  strncpy (p->a[2], s, sizeof p->a[2]);
+  strncpy ((char*)p->b[2], s, sizeof p->b[2]);
+  strncpy ((char*)p->c[2], s, sizeof p->c[2]);
+}
+
+struct Pointers
+{
+  char NONSTR (*p)[4];
+  signed char NONSTR (*q)[4];
+  unsigned char NONSTR (*r)[4];
+};
+
+void test_pointers (struct Pointers *p)
+{
+  strncpy (*p->p, S, sizeof S - 1);
+  strncpy ((char*)*p->q, S, sizeof S - 1);
+  strncpy ((char*)*p->r, S, sizeof S - 1);
+}
+
+struct ConstArrays
+{
+  const char NONSTR a[4][4];
+  const signed char NONSTR b[4][4];
+  const unsigned char NONSTR c[4][4];
+};
+
+void test_const_arrays (struct ConstArrays *p, const char *s)
+{
+  strncpy ((char*)p->a[2], s, sizeof p->a[2]);
+  strncpy ((char*)p->b[2], s, sizeof p->b[2]);
+  strncpy ((char*)p->c[2], s, sizeof p->c[2]);
+}
+
+struct ConstPointers
+{
+  const char NONSTR (*p)[4];
+  const signed char NONSTR (*q)[4];
+  const unsigned char NONSTR (*r)[4];
+};
+
+void test_const_pointers (struct ConstPointers *p)
+{
+  strncpy ((char*)*p->p, S, sizeof S - 1);
+  strncpy ((char*)*p->q, S, sizeof S - 1);
+  strncpy ((char*)*p->r, S, sizeof S - 1);
+}
+
+struct VolatileArrays
+{
+  volatile char NONSTR a[4][4];
+  volatile signed char NONSTR b[4][4];
+  volatile unsigned char NONSTR c[4][4];
+};
+
+void test_volatile_arrays (struct VolatileArrays *p, const char *s)
+{
+  strncpy ((char*)p->a[2], s, sizeof p->a[2]);
+  strncpy ((char*)p->b[2], s, sizeof p->b[2]);
+  strncpy ((char*)p->c[2], s, sizeof p->c[2]);
+}
+
+struct VolatilePointers
+{
+  volatile char NONSTR (*p)[4];
+  volatile signed char NONSTR (*q)[4];
+  volatile unsigned char NONSTR (*r)[4];
+};
+
+void test_volatile_pointers (struct VolatilePointers *p)
+{
+  strncpy ((char*)*p->p, S, sizeof S - 1);
+  strncpy ((char*)*p->q, S, sizeof S - 1);
+  strncpy ((char*)*p->r, S, sizeof S - 1);
+}
+
+struct ConstVolatileArrays
+{
+  const volatile char NONSTR a[4][4];
+  const volatile signed char NONSTR b[4][4];
+  const volatile unsigned char NONSTR c[4][4];
+};
+
+void test_const_volatile_arrays (struct ConstVolatileArrays *p, const char *s)
+{
+  strncpy ((char*)p->a[2], s, sizeof p->a[2]);
+  strncpy ((char*)p->b[2], s, sizeof p->b[2]);
+  strncpy ((char*)p->c[2], s, sizeof p->c[2]);
+}
+
+struct ConstVolatilePointers
+{
+  const volatile char NONSTR (*p)[4];
+  const volatile signed char NONSTR (*q)[4];
+  const volatile unsigned char NONSTR (*r)[4];
+};
+
+void test_const_volatile_pointers (struct ConstVolatilePointers *p)
+{
+  strncpy ((char*)*p->p, S, sizeof S - 1);
+  strncpy ((char*)*p->q, S, sizeof S - 1);
+  strncpy ((char*)*p->r, S, sizeof S - 1);
+}
+
+/* { dg-prune-output "-Wdiscarded-qualifiers" } */
--- gcc/testsuite/c-c++-common/attr-nonstring-14.c.jj   2025-02-14 
11:08:44.035409040 +0100
+++ gcc/testsuite/c-c++-common/attr-nonstring-14.c      2025-02-14 
11:10:00.037343222 +0100
@@ -0,0 +1,184 @@
+/* PR 85623 - strncmp() warns about attribute 'nonstring' incorrectly
+   in -Wstringop-overflow
+  { dg-do compile }
+  { dg-options "-O2 -Wstringop-overread -ftrack-macro-expansion=0" } */
+
+#include "../gcc.dg/range.h"
+
+#if __cplusplus
+extern "C" {
+#endif
+
+extern int strcmp (const char*, const char*);
+extern int strncmp (const char*, const char*, size_t);
+extern int strncasecmp (const char*, const char*, size_t);
+
+extern size_t strspn (const char*, const char*);
+extern size_t strcspn (const char*, const char*);
+
+#if __cplusplus
+}
+#endif
+
+#define S26 "0123456789abcdefghijklmnopqrstuvwxyz"
+#define S(n) (S26 + sizeof S26 - 1 - (n))
+
+char __attribute__ ((nonstring)) a3[3][3];
+
+void sink (int);
+
+#define T(call)   sink (call)
+
+void test_strcmp_cst (void)
+{
+  /* Verify that no warning is issued for strcmp() calls with a non-string
+     array argument when the other argument is a string whose length is
+     less than the size of the array.  Because the function stops reading
+     at the first nul character there is no chance that it will read past
+     the end of the array.  */
+  T (strcmp (S (0), a3[2]));
+  T (strcmp (S (1), a3[2]));
+  T (strcmp (S (2), a3[2]));
+  /* The following reads a3[2][3].  */
+  T (strcmp (S (3), a3[2]));   /* { dg-warning "\\\[-Wstringop-overread" } */
+  /* The following also reads past the end of a3[2].  */
+  T (strcmp (S (9), a3[2]));   /* { dg-warning "\\\[-Wstringop-overread" } */
+
+  T (strcmp (a3[2], S (0)));
+  T (strcmp (a3[2], S (1)));
+  T (strcmp (a3[2], S (2)));
+  T (strcmp (a3[2], S (3)));   /* { dg-warning "\\\[-Wstringop-overread" } */
+  T (strcmp (a3[2], S (9)));   /* { dg-warning "\\\[-Wstringop-overread" } */
+}
+
+
+void test_strcmp_range (const char *s)
+{
+  s = signed_value () < 0 ? S (0) : S (1);
+  T (strcmp (a3[2], s));
+
+  s = signed_value () < 0 ? S (0) : S (2);
+  T (strcmp (a3[2], s));
+
+  s = signed_value () < 0 ? S (0) : S (3);
+  T (strcmp (a3[2], s));       /* { dg-warning "\\\[-Wstringop-overread" } */
+
+  s = signed_value () < 0 ? S (1) : S (2);
+  T (strcmp (a3[2], s));
+
+  s = signed_value () < 0 ? S (1) : S (3);
+  T (strcmp (a3[2], s));       /* { dg-warning "\\\[-Wstringop-overread" } */
+
+  s = signed_value () < 0 ? S (3) : S (4);
+  T (strcmp (a3[2], s));       /* { dg-warning "\\\[-Wstringop-overread" } */
+}
+
+
+void test_strncmp_cst (void)
+{
+  T (strncmp (S (0), a3[2], 1));
+  T (strncmp (S (1), a3[2], 2));
+  T (strncmp (S (2), a3[2], 3));
+  T (strncmp (S (3), a3[2], 3));
+  T (strncmp (S (3), a3[2], 4));   /* { dg-warning "\\\[-Wstringop-overread" } 
*/
+
+  T (strncmp (S (9), a3[2], 3));
+  T (strncmp (S (9), a3[2], 4));   /* { dg-warning "\\\[-Wstringop-overread" } 
*/
+  T (strncmp (S (9), a3[2], 5));   /* { dg-warning "\\\[-Wstringop-overread" } 
*/
+
+  T (strncmp (a3[2], S (0), 1));
+  T (strncmp (a3[2], S (1), 2));
+  T (strncmp (a3[2], S (2), 3));
+  T (strncmp (a3[2], S (3), 3));
+  T (strncmp (a3[2], S (3), 4));   /* { dg-warning "\\\[-Wstringop-overread" } 
*/
+
+  T (strncmp (a3[2], S (9), 3));
+  T (strncmp (a3[2], S (9), 4));   /* { dg-warning "\\\[-Wstringop-overread" } 
*/
+  T (strncmp (a3[2], S (9), 5));   /* { dg-warning "\\\[-Wstringop-overread" } 
*/
+}
+
+void test_strncmp_range (const char *s)
+{
+  T (strncmp (a3[2], S (2), UR (0, 3)));
+  T (strncmp (a3[2], S (2), UR (1, 4)));
+  T (strncmp (a3[2], S (2), UR (2, 5)));
+  T (strncmp (a3[2], S (2), UR (3, 6)));
+  T (strncmp (a3[2], S (2), UR (4, 7)));
+
+  T (strncmp (a3[2], S (5), UR (0, 3)));
+  T (strncmp (a3[2], S (5), UR (1, 4)));
+  T (strncmp (a3[2], S (5), UR (2, 5)));
+  T (strncmp (a3[2], S (5), UR (3, 6)));
+  T (strncmp (a3[2], S (5), UR (4, 7)));   /* { dg-warning 
"\\\[-Wstringop-overread" } */
+  T (strncmp (a3[2], S (5), UR (7, 9)));   /* { dg-warning 
"\\\[-Wstringop-overread" } */
+
+  s = signed_value () < 0 ? S (0) : S (1);
+  T (strncmp (a3[2], s, UR (1, 3)));
+  T (strncmp (a3[2], s, UR (2, 5)));
+
+  s = signed_value () < 0 ? S (2) : S (5);
+  T (strncmp (a3[2], s, UR (1, 3)));
+
+  s = signed_value () < 0 ? S (2) : S (5);
+  T (strncmp (a3[2], s, UR (1, 4)));
+  T (strncmp (a3[2], s, UR (2, 5)));
+  T (strncmp (a3[2], s, UR (3, 6)));
+  T (strncmp (a3[2], s, UR (4, 7)));       /* { dg-warning 
"\\\[-Wstringop-overread" } */
+}
+
+void test_strncasecmp (void)
+{
+  T (strncasecmp (S (0), a3[2], 1));
+  T (strncasecmp (S (1), a3[2], 2));
+  T (strncasecmp (S (2), a3[2], 3));
+  T (strncasecmp (S (3), a3[2], 3));
+  T (strncasecmp (S (3), a3[2], 4));   /* { dg-warning 
"\\\[-Wstringop-overread" } */
+
+  T (strncasecmp (S (9), a3[2], 3));
+  T (strncasecmp (S (9), a3[2], 4));   /* { dg-warning 
"\\\[-Wstringop-overread" } */
+  T (strncasecmp (S (9), a3[2], 5));   /* { dg-warning 
"\\\[-Wstringop-overread" } */
+
+  T (strncasecmp (a3[2], S (0), 1));
+  T (strncasecmp (a3[2], S (1), 2));
+  T (strncasecmp (a3[2], S (2), 3));
+  T (strncasecmp (a3[2], S (3), 3));
+  T (strncasecmp (a3[2], S (3), 4));   /* { dg-warning 
"\\\[-Wstringop-overread" } */
+
+  T (strncasecmp (a3[2], S (9), 3));
+  T (strncasecmp (a3[2], S (9), 4));   /* { dg-warning 
"\\\[-Wstringop-overread" } */
+  T (strncasecmp (a3[2], S (9), 5));   /* { dg-warning 
"\\\[-Wstringop-overread" } */
+}
+
+void test_strspn (void)
+{
+  /* strspn must traverse all characters in the second argument except
+     when the first string is empty. */
+  T (strspn (S (0), a3[2]));
+  T (strspn (S (1), a3[2]));   /* { dg-warning "\\\[-Wstringop-overread" } */
+  T (strspn (S (2), a3[2]));   /* { dg-warning "\\\[-Wstringop-overread" } */
+  T (strspn (S (3), a3[2]));   /* { dg-warning "\\\[-Wstringop-overread" } */
+  T (strspn (S (9), a3[2]));   /* { dg-warning "\\\[-Wstringop-overread" } */
+
+  /* Similarly, strspn must traverse all characters in the first argument
+     except when the second string is empty. */
+  T (strspn (a3[2], S (0)));
+  T (strspn (a3[2], S (1)));   /* { dg-warning "\\\[-Wstringop-overread" } */
+  T (strspn (a3[2], S (2)));   /* { dg-warning "\\\[-Wstringop-overread" } */
+  T (strspn (a3[2], S (3)));   /* { dg-warning "\\\[-Wstringop-overread" } */
+  T (strspn (a3[2], S (9)));   /* { dg-warning "\\\[-Wstringop-overread" } */
+}
+
+void test_strcspn (void)
+{
+  T (strcspn (S (0), a3[2]));
+  T (strcspn (S (1), a3[2]));   /* { dg-warning "\\\[-Wstringop-overread" } */
+  T (strcspn (S (2), a3[2]));   /* { dg-warning "\\\[-Wstringop-overread" } */
+  T (strcspn (S (3), a3[2]));   /* { dg-warning "\\\[-Wstringop-overread" } */
+  T (strcspn (S (9), a3[2]));   /* { dg-warning "\\\[-Wstringop-overread" } */
+
+  T (strcspn (a3[2], S (0)));   /* { dg-warning "\\\[-Wstringop-overread" } */
+  T (strcspn (a3[2], S (1)));   /* { dg-warning "\\\[-Wstringop-overread" } */
+  T (strcspn (a3[2], S (2)));   /* { dg-warning "\\\[-Wstringop-overread" } */
+  T (strcspn (a3[2], S (3)));   /* { dg-warning "\\\[-Wstringop-overread" } */
+  T (strcspn (a3[2], S (9)));   /* { dg-warning "\\\[-Wstringop-overread" } */
+}
--- gcc/testsuite/c-c++-common/attr-nonstring-15.c.jj   2025-02-14 
11:10:27.145963052 +0100
+++ gcc/testsuite/c-c++-common/attr-nonstring-15.c      2025-02-14 
11:12:30.594231859 +0100
@@ -0,0 +1,90 @@
+/* PR 85643 - attribute nonstring fails to squash -Wstringop-truncation
+   warning
+  { dg-do compile }
+  { dg-options "-O2 -Wall -ftrack-macro-expansion=0" } */
+
+#define strncpy   __builtin_strncpy
+
+struct A {
+  char a[2][16 + 1];
+};
+
+struct B {
+  char a[2][16] __attribute__ ((__nonstring__));
+};
+
+struct B*
+test_memarray (const struct A *s)
+{
+  static struct B b;
+  strncpy (b.a[1], s->a[1], sizeof b.a[1]);
+  return &b;
+}
+
+const char*
+test_array (const char *s)
+{
+  static char a[2][80] __attribute__ ((__nonstring__));
+  strncpy (a[1], s, sizeof a[1]);
+  return a[1];
+}
+
+const char*
+test_array_idx (const char *s)
+{
+  static char a[2][80]  __attribute__ ((__nonstring__));
+  char *p __attribute__ ((__nonstring__)) = &a[1][20];
+  strncpy (p, s, 60);   /* { dg-bogus "-Wstringop-truncation" } */
+  return a;
+}
+
+const char*
+test_array_off (const char *s)
+{
+  static char a[2][80]  __attribute__ ((__nonstring__));
+  char *p __attribute__ ((__nonstring__)) = a[1] + 20;
+  strncpy (p, s, 60);   /* { dg-bogus "-Wstringop-truncation" } */
+  return a;
+}
+
+struct B*
+test_memarray_cstidx_idx (const char *s)
+{
+  static struct B b[2];
+  char *p __attribute__ ((__nonstring__)) = &b[1].a[1][4];
+
+  /* The destination below is represented as &MEM[(void *)&a + 20B] and
+     which (in general) doesn't make it possible to determine what member
+     it refers to.  */
+  strncpy (p, s, sizeof b[1].a[1] - 4);   /* { dg-bogus 
"-Wstringop-truncation" } */
+  return b;
+}
+
+struct B*
+test_memarray_cstidx_off (const char *s)
+{
+  static struct B b[2];
+  char *p __attribute__ ((__nonstring__)) = b[1].a[1] + 4;
+
+  /* Same as above.  */
+  strncpy (p, s, sizeof b[1].a[1] - 4);   /* { dg-bogus 
"-Wstringop-truncation" "" { xfail *-*-*} } */
+  return b;
+}
+
+struct B*
+test_memarray_varidx_idx (const char *s, int i)
+{
+  static struct B b[3];
+  char *p __attribute__ ((__nonstring__)) = &b[i].a[1][4];
+  strncpy (p, s, sizeof b[i].a[1] - 4);
+  return b;
+}
+
+struct B*
+test_memarray_varidx_off (const char *s, int i)
+{
+  static struct B b[3];
+  char *p __attribute__ ((__nonstring__)) = b[i].a[1] + 4;
+  strncpy (p, s, sizeof b[i].a[1] - 4);
+  return b;
+}
--- gcc/testsuite/c-c++-common/attr-nonstring-16.c.jj   2025-02-14 
11:13:08.484700495 +0100
+++ gcc/testsuite/c-c++-common/attr-nonstring-16.c      2025-02-14 
11:14:36.841461413 +0100
@@ -0,0 +1,147 @@
+/* PR middle-end/85602 - -Wsizeof-pointer-memaccess for strncat with size
+   of source
+   { dg-do compile }
+   { dg-options "-O2 -Wno-array-bounds -Wsizeof-pointer-memaccess 
-Wstringop-truncation -ftrack-macro-expansion=0" } */
+
+#include "../gcc.dg/range.h"
+
+typedef __SIZE_TYPE__ size_t;
+
+#if __cplusplus
+extern "C" {
+#endif
+
+char* strcpy (char*, const char*);
+size_t strlen (const char*);
+char* strncat (char*, const char*, __SIZE_TYPE__);
+char* strncpy (char*, const char*, __SIZE_TYPE__);
+
+#if __cplusplus
+}
+#endif
+
+#define NONSTR __attribute__ ((nonstring))
+
+NONSTR char nd3[3][3];
+NONSTR char nd4[4][4];
+NONSTR char nd5[5][5];
+
+NONSTR char ns3[3][3];
+NONSTR char ns4[4][4];
+NONSTR char ns5[5][5];
+
+NONSTR char* pns;
+
+void sink (void*, ...);
+
+#define T(call) sink (call)
+
+/* Verify that for a nonstring source array of an unknown length
+   a warning is issued only when the bound exceeds the array size.  */
+
+void test_strncat_nonstring_cst (char *d)
+{
+  T (strncat (d, ns3[1], 1));
+  T (strncat (d, ns3[1], 2));
+  T (strncat (d, ns3[1], 3));
+  T (strncat (d, ns3[1], sizeof ns3[1]));
+  T (strncat (d, ns3[1], 4));     /* { dg-warning "argument 2 declared 
attribute .nonstring. is smaller than the specified bound 4" } */
+
+  T (strncat (d, ns4[1], 1));
+  T (strncat (d, ns4[1], 2));
+  T (strncat (d, ns4[1], 3));
+  T (strncat (d, ns4[1], 4));
+  T (strncat (d, ns4[1], sizeof ns4[1]));
+  T (strncat (d, ns4[1], 5));     /* { dg-warning "argument 2 declared 
attribute .nonstring. is smaller than the specified bound 5" } */
+
+  T (strncat (nd3[1], ns3[1], 1));
+  T (strncat (nd3[1], ns3[1], 2));
+  T (strncat (nd3[1], ns3[1], 3));     /* { dg-warning "specified bound 3 
equals destination size" } */
+  /* Either of the two warnings below is fine.  */
+  T (strncat (nd3[1], ns3[1], 4));     /* { dg-warning "argument 2 declared 
attribute .nonstring. is smaller than the specified bound 4|specified bound 4 
exceeds destination size 3" } */
+
+  T (strncat (d, pns, sizeof pns));   /* { dg-warning "argument to .sizeof. in 
.\[^\n\r\]*strncat\[^\n\r\]*. call is the same expression as the source" } */
+}
+
+
+void test_strncat_nonstring_var (char *d, size_t n)
+{
+  /* In the following the bound coulld apply to either the destination
+     or the source.  The expected use of strncat() is to pass it as
+     the bound DSIZE - strlen(D) - 1 so the call below is diagnosed.  */
+  T (strncat (d, ns3[1], n));            /* { dg-warning "argument 2 declared 
attribute .nonstring." } */
+
+  T (strncat (d, ns3[1], UR (0, 1)));
+  T (strncat (d, ns3[1], UR (1, 2)));
+  T (strncat (d, ns3[1], UR (2, 3)));
+  T (strncat (d, ns3[1], UR (3, 4)));    /* { dg-warning "argument 2 declared 
attribute 'nonstring' may be smaller than the specified bound \\\[3, 4]" } */
+  T (strncat (d, ns3[1], UR (4, 5)));    /* { dg-warning "argument 2 declared 
attribute 'nonstring' is smaller than the specified bound \\\[4, 5]" } */
+
+  /* Verify that the call below (the intended use of strncat()) is
+     also diagnosed.  */
+  T (strncat (d, ns3[1], 256 - strlen (d) - 1));   /* { dg-warning "argument 2 
declared attribute .nonstring." } */
+
+  T (strncat (nd3[1], ns5[1], UR (0, 1)));
+  T (strncat (nd3[1], ns5[1], UR (1, 2)));
+  T (strncat (nd3[1], ns5[1], UR (2, 3)));
+  T (strncat (nd3[1], ns5[1], UR (3, 4)));
+  T (strncat (nd3[1], ns5[1], UR (4, 5)));  /* { dg-warning "specified bound 
\\\[4, 5] exceeds destination size 3" } */
+
+  T (strncat (nd5[1], ns3[1], UR (0, 1)));
+  T (strncat (nd5[1], ns3[1], UR (1, 2)));
+  T (strncat (nd5[1], ns3[1], UR (2, 3)));
+  T (strncat (nd5[1], ns3[1], UR (3, 4)));  /* { dg-warning "argument 2 
declared attribute 'nonstring' may be smaller than the specified bound \\\[3, 
4]" } */
+}
+
+/* Verify that for a nonstring source array of a known length (i.e.,
+   a nonstring array containing a nul-terminated string) a warning
+   is issued only for certain truncation.
+
+   The test cases are split up to work around bug 81343 (or one like
+   it).  */
+
+void test_strncat_string_1_1 (char *d)
+{
+  strcpy (ns3[1], "1");
+  T (strncat (d, ns3[1], 1));    /* { dg-warning "output truncated before 
terminating nul copying 1 byte from a string of the same length" } */
+}
+
+void test_strncat_string_1_2 (char *d)
+{
+  strcpy (ns3[1], "1");
+  T (strncat (d, ns3[1], 2));
+}
+
+void test_strncat_string_1_3 (char *d)
+{
+  strcpy (ns3[1], "1");
+  T (strncat (d, ns3[1], 3));
+}
+
+void test_strncat_string_2_1 (char *d)
+{
+  strcpy (ns3[1], "12");
+  T (strncat (d, ns3[1], 1));    /* { dg-warning "output truncated copying 1 
byte from a string of length 2" } */
+}
+
+void test_strncat_string_2_2 (char *d)
+{
+  strcpy (ns3[1], "12");
+  T (strncat (d, ns3[1], 2));    /* { dg-warning "output truncated before 
terminating nul copying 2 bytes from a string of the same length" } */
+}
+
+void test_strncat_string_2_3 (char *d)
+{
+  strcpy (ns3[1], "12");
+  T (strncat (d, ns3[1], 3));
+}
+
+
+void test_strcncpy_nonstring_cst (char *d)
+{
+  T (strncpy (d, ns3[1], 1));
+  T (strncpy (d, ns3[1], 2));
+  T (strncpy (d, ns3[1], 3));
+  T (strncpy (d, ns3[1], sizeof ns3[1]));
+  T (strncpy (d, ns3[1], 4));      /* { dg-warning "argument 2 declared 
attribute .nonstring. is smaller than the specified bound 4" } */
+}

        Jakub

Reply via email to