PR middle-end/98097 - missing -Wstring-compare with a member array

gcc/ChangeLog:

	PR middle-end/98097
	* builtins.c (unterminated_array): Adjust to a name change.  Adjust
	indentation.
	(c_strlen): Use a member instead of a local variable.
	(expand_builtin_stpcpy_1): Adjust to a name change.
	(fold_builtin_strlen): Same.
	* builtins.h (struct c_strlen_data::nonstr): New data member to use
	instead of decl.
	 (struct c_strlen_data::decl): Adjust comment.
	* gimple-fold.c (get_range_strlen_tree): Set c_strlen_data::nonstr
	in addition to c_strlen_data::decl.
	(get_maxval_strlen): Adjust to a name change.
	(gimple_fold_builtin_stpcpy): Same.
	(gimple_fold_builtin_strlen): Same.
	* gimple-ssa-sprintf.c (get_string_length): Same.
	* tree-ssa-strlen.c (get_range_strlen_dynamic): Same.  Also set
	struct c_strlen_data::decl.
	(get_len_or_size): Use c_strlen_data::decl.  Succeed even for
	nonconstant member arrays.
	(strxcmp_eqz_result): Handle member arrays.
	(handle_builtin_string_cmp): Issue warnings for member arrays.

gcc/testsuite/ChangeLog:

	PR middle-end/98097
	* gcc.dg/Wstring-compare.c:
	* gcc.dg/strcmpopt_10.c:
	* gcc.dg/Wstring-compare-4.c: New test.
	* gcc.dg/Wstring-compare-5.c: New test.

diff --git a/gcc/builtins.c b/gcc/builtins.c
index ffbb9b7f5f1..9b7a82153c8 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -1253,42 +1253,41 @@ check_nul_terminated_array (tree expr, tree src,
 tree
 unterminated_array (tree exp, tree *size /* = NULL */, bool *exact /* = NULL */)
 {
-  /* C_STRLEN will return NULL and set DECL in the info
-     structure if EXP references a unterminated array.  */
+  /* C_STRLEN will return NULL and set LENDATA.NONSTR to the DECL
+     of the unterminated array if EXP references one.  */
   c_strlen_data lendata = { };
   tree len = c_strlen (exp, 1, &lendata);
-  if (len == NULL_TREE && lendata.minlen && lendata.decl)
-     {
-       if (size)
+  if (len || !lendata.minlen || !lendata.nonstr)
+    return NULL_TREE;
+
+  if (size)
+    {
+      len = lendata.minlen;
+      if (lendata.off)
 	{
-	  len = lendata.minlen;
-	  if (lendata.off)
+	  /* Constant offsets are already accounted for in LENDATA.MINLEN,
+	     but not in a SSA_NAME + CST expression.  */
+	  if (TREE_CODE (lendata.off) == INTEGER_CST)
+	    *exact = true;
+	  else if (TREE_CODE (lendata.off) == PLUS_EXPR
+		   && TREE_CODE (TREE_OPERAND (lendata.off, 1)) == INTEGER_CST)
 	    {
-	      /* Constant offsets are already accounted for in LENDATA.MINLEN,
-		 but not in a SSA_NAME + CST expression.  */
-	      if (TREE_CODE (lendata.off) == INTEGER_CST)
-		*exact = true;
-	      else if (TREE_CODE (lendata.off) == PLUS_EXPR
-		       && TREE_CODE (TREE_OPERAND (lendata.off, 1)) == INTEGER_CST)
-		{
-		  /* Subtract the offset from the size of the array.  */
-		  *exact = false;
-		  tree temp = TREE_OPERAND (lendata.off, 1);
-		  temp = fold_convert (ssizetype, temp);
-		  len = fold_build2 (MINUS_EXPR, ssizetype, len, temp);
-		}
-	      else
-		*exact = false;
+	      /* Subtract the offset from the size of the array.  */
+	      *exact = false;
+	      tree temp = TREE_OPERAND (lendata.off, 1);
+	      temp = fold_convert (ssizetype, temp);
+	      len = fold_build2 (MINUS_EXPR, ssizetype, len, temp);
 	    }
 	  else
-	    *exact = true;
-
-	  *size = len;
+	    *exact = false;
 	}
-       return lendata.decl;
-     }
+      else
+	*exact = true;
 
-  return NULL_TREE;
+      *size = len;
+    }
+
+  return lendata.nonstr;
 }
 
 /* Compute the length of a null-terminated character string or wide
@@ -1353,8 +1352,7 @@ c_strlen (tree arg, int only_value, c_strlen_data *data, unsigned eltsize)
   /* Offset from the beginning of the string in bytes.  */
   tree byteoff;
   tree memsize;
-  tree decl;
-  src = string_constant (src, &byteoff, &memsize, &decl);
+  src = string_constant (src, &byteoff, &memsize, &data->decl);
   if (src == 0)
     return NULL_TREE;
 
@@ -1399,7 +1397,7 @@ c_strlen (tree arg, int only_value, c_strlen_data *data, unsigned eltsize)
 	return NULL_TREE;
       else if (len >= maxelts)
 	{
-	  data->decl = decl;
+	  data->nonstr = data->decl;
 	  data->off = byteoff;
 	  data->minlen = ssize_int (len);
 	  return NULL_TREE;
@@ -1449,8 +1447,9 @@ c_strlen (tree arg, int only_value, c_strlen_data *data, unsigned eltsize)
 			 "offset %qwi outside bounds of constant string",
 			 eltoff))
 	{
-	  if (decl)
-	    inform (DECL_SOURCE_LOCATION (decl), "%qE declared here", decl);
+	  if (data->decl)
+	    inform (DECL_SOURCE_LOCATION (data->decl), "%qE declared here",
+		    data->decl);
 	  TREE_NO_WARNING (arg) = 1;
 	}
       return NULL_TREE;
@@ -1470,14 +1469,15 @@ c_strlen (tree arg, int only_value, c_strlen_data *data, unsigned eltsize)
   unsigned len = string_length (ptr + eltoff * eltsize, eltsize,
 				strelts - eltoff);
 
-  /* Don't know what to return if there was no zero termination.
-     Ideally this would turn into a gcc_checking_assert over time.
-     Set DECL/LEN so callers can examine them.  */
   if (len >= maxelts - eltoff)
     {
-      data->decl = decl;
+      /* The array is not nul-termimated. Set NONSTR/LEN so callers can
+	 examine them.
+	 FIXME: Return a failure other than null (e.g., error_mark_node)
+	 instead. */
       data->off = byteoff;
       data->minlen = ssize_int (len);
+      data->nonstr = data->decl;
       return NULL_TREE;
     }
 
@@ -6231,8 +6231,8 @@ expand_builtin_stpcpy_1 (tree exp, rtx target, machine_mode mode)
 	return expand_movstr (dst, src, target,
 			      /*retmode=*/ RETURN_END_MINUS_ONE);
 
-      if (lendata.decl)
-	warn_string_no_nul (EXPR_LOCATION (exp), exp, NULL, src, lendata.decl);
+      if (lendata.nonstr)
+	warn_string_no_nul (EXPR_LOCATION (exp), exp, NULL, src, lendata.nonstr);
 
       lenp1 = size_binop_loc (loc, PLUS_EXPR, len, ssize_int (1));
       ret = expand_builtin_mempcpy_args (dst, src, lenp1,
@@ -10907,16 +10907,16 @@ fold_builtin_strlen (location_t loc, tree expr, tree type, tree arg)
       if (len)
 	return fold_convert_loc (loc, type, len);
 
-      if (!lendata.decl)
+      if (!lendata.nonstr)
 	c_strlen (arg, 1, &lendata);
 
-      if (lendata.decl)
+      if (lendata.nonstr)
 	{
 	  if (EXPR_HAS_LOCATION (arg))
 	    loc = EXPR_LOCATION (arg);
 	  else if (loc == UNKNOWN_LOCATION)
 	    loc = input_location;
-	  warn_string_no_nul (loc, expr, "strlen", arg, lendata.decl);
+	  warn_string_no_nul (loc, expr, "strlen", arg, lendata.nonstr);
 	}
 
       return NULL_TREE;
diff --git a/gcc/builtins.h b/gcc/builtins.h
index 642923281c1..e021adbe7d7 100644
--- a/gcc/builtins.h
+++ b/gcc/builtins.h
@@ -91,15 +91,16 @@ struct c_strlen_data
   tree minlen;
   tree maxlen;
   tree maxbound;
-  /* When non-null, DECL refers to the declaration known to store
-     an unterminated constant character array, as in:
-     const char s[] = { 'a', 'b', 'c' };
-     It is used to diagnose uses of such arrays in functions such as
-     strlen() that expect a nul-terminated string as an argument.  */
+  /* When non-null, stores the DECL or expression based on which
+     the data was obtained.  */
   tree decl;
   /* Non-constant offset from the beginning of a string not accounted
      for in the length range.  Used to improve diagnostics.  */
   tree off;
+  /* Set to the DECL of an initialized but unterminated const array.
+     Used to diagnose uses of such arrays in functions such as strlen()
+     that expect a nul-terminated string as an argument.  */
+  tree nonstr;
 };
 
 extern tree c_strlen (tree, int, c_strlen_data * = NULL, unsigned = 1);
diff --git a/gcc/gimple-fold.c b/gcc/gimple-fold.c
index 3148c6b84d9..235be08d638 100644
--- a/gcc/gimple-fold.c
+++ b/gcc/gimple-fold.c
@@ -1367,6 +1367,10 @@ get_range_strlen_tree (tree arg, bitmap *visited, strlen_range_kind rkind,
 	}
     }
 
+  /* The DECL, COMPONENT_REF, or MEM_REF referencing the string used
+     to set either MAXBOUND or MAXLEN.  */
+  tree decl = arg;
+
   if (rkind == SRK_INT_VALUE)
     {
       /* We are computing the maximum value (not string length).  */
@@ -1379,13 +1383,13 @@ get_range_strlen_tree (tree arg, bitmap *visited, strlen_range_kind rkind,
     {
       c_strlen_data lendata = { };
       val = c_strlen (arg, 1, &lendata, eltsize);
-
-      if (!val && lendata.decl)
+      decl = lendata.decl;
+      if (!val && lendata.nonstr)
 	{
 	  /* ARG refers to an unterminated const character array.
 	     DATA.DECL with size DATA.LEN.  */
 	  val = lendata.minlen;
-	  pdata->decl = lendata.decl;
+	  pdata->nonstr = lendata.nonstr;
 	}
     }
 
@@ -1401,7 +1405,8 @@ get_range_strlen_tree (tree arg, bitmap *visited, strlen_range_kind rkind,
 
       if (TREE_CODE (arg) == ARRAY_REF)
 	{
-	  tree optype = TREE_TYPE (TREE_OPERAND (arg, 0));
+	  decl = TREE_OPERAND (arg, 0);
+	  tree optype = TREE_TYPE (decl);
 
 	  /* Determine the "innermost" array type.  */
 	  while (TREE_CODE (optype) == ARRAY_TYPE
@@ -1547,6 +1552,8 @@ get_range_strlen_tree (tree arg, bitmap *visited, strlen_range_kind rkind,
 
   if (pdata->maxbound && TREE_CODE (pdata->maxbound) == INTEGER_CST)
     {
+      pdata->decl = decl;
+
       /* Adjust the tighter (more optimistic) string length bound
 	 if necessary and proceed to adjust the more conservative
 	 bound.  */
@@ -1559,13 +1566,18 @@ get_range_strlen_tree (tree arg, bitmap *visited, strlen_range_kind rkind,
 	pdata->maxbound = val;
     }
   else if (pdata->maxbound || maxbound)
-    /* Set PDATA->MAXBOUND only if it either isn't INTEGER_CST or
-       if VAL corresponds to the maximum length determined based
-       on the type of the object.  */
-    pdata->maxbound = val;
+    {
+      /* Set PDATA->MAXBOUND only if it either isn't INTEGER_CST or
+	 if VAL corresponds to the maximum length determined based
+	 on the type of the object.  */
+      pdata->maxbound = val;
+      pdata->decl = decl;
+    }
 
   if (tight_bound)
     {
+      decl = NULL_TREE;
+
       /* VAL computed above represents an optimistically tight bound
 	 on the length of the string based on the referenced object's
 	 or subobject's type.  Determine the conservative upper bound
@@ -1593,6 +1605,7 @@ get_range_strlen_tree (tree arg, bitmap *visited, strlen_range_kind rkind,
 	    val = build_all_ones_cst (size_type_node);
 	  else
 	    {
+	      decl = base;
 	      val = DECL_SIZE_UNIT (base);
 	      val = fold_build2 (MINUS_EXPR, TREE_TYPE (val), val,
 				 size_int (offset + 1));
@@ -1613,7 +1626,11 @@ get_range_strlen_tree (tree arg, bitmap *visited, strlen_range_kind rkind,
 	    return false;
 
 	  if (tree_int_cst_lt (pdata->maxlen, val))
-	    pdata->maxlen = val;
+	    {
+	      if (decl)
+		pdata->decl = decl;
+	      pdata->maxlen = val;
+	    }
 	  return true;
 	}
       else if (simple_cst_equal (val, pdata->maxlen) != 1)
@@ -1625,6 +1642,9 @@ get_range_strlen_tree (tree arg, bitmap *visited, strlen_range_kind rkind,
     }
 
   pdata->maxlen = val;
+  if (decl)
+    pdata->decl = decl;
+
   return rkind == SRK_LENRANGE || !integer_all_onesp (val);
 }
 
@@ -1816,15 +1836,18 @@ get_maxval_strlen (tree arg, strlen_range_kind rkind, tree *nonstr = NULL)
 
   if (nonstr)
     {
-      /* For callers prepared to handle unterminated arrays set
-	 *NONSTR to point to the declaration of the array and return
-	 the maximum length/size. */
-      *nonstr = lendata.decl;
+      /* For callers prepared to handle unterminated arrays set *NONSTR
+	 to point to the declaration of the array and return the maximum
+	 length/size. */
+      *nonstr = lendata.nonstr;
       return lendata.maxlen;
     }
 
-  /* Fail if the constant array isn't nul-terminated.  */
-  return lendata.decl ? NULL_TREE : lendata.maxlen;
+  if (lendata.nonstr)
+    /* Fail if the constant array isn't nul-terminated.  */
+    return NULL_TREE;
+
+  return lendata.maxlen;
 }
 
 
@@ -3083,16 +3106,16 @@ gimple_fold_builtin_stpcpy (gimple_stmt_iterator *gsi)
   if (!len
       || TREE_CODE (len) != INTEGER_CST)
     {
-      data.decl = unterminated_array (src, &size, &exact);
-      if (!data.decl)
+      data.nonstr = unterminated_array (src, &size, &exact);
+      if (!data.nonstr)
 	return false;
     }
 
-  if (data.decl)
+  if (data.nonstr)
     {
       /* Avoid folding calls with unterminated arrays.  */
       if (!gimple_no_warning_p (stmt))
-	warn_string_no_nul (loc, NULL_TREE, "stpcpy", src, data.decl, size,
+	warn_string_no_nul (loc, NULL_TREE, "stpcpy", src, data.nonstr, size,
 			    exact);
       gimple_set_no_warning (stmt, true);
       return false;
@@ -3846,7 +3869,7 @@ gimple_fold_builtin_strlen (gimple_stmt_iterator *gsi)
 
   c_strlen_data lendata = { };
   if (get_range_strlen (arg, &lendata, /* eltsize = */ 1)
-      && !lendata.decl
+      && !lendata.nonstr
       && lendata.minlen && TREE_CODE (lendata.minlen) == INTEGER_CST
       && lendata.maxlen && TREE_CODE (lendata.maxlen) == INTEGER_CST)
     {
diff --git a/gcc/gimple-ssa-sprintf.c b/gcc/gimple-ssa-sprintf.c
index ca0572f53d3..dd3000bebb0 100644
--- a/gcc/gimple-ssa-sprintf.c
+++ b/gcc/gimple-ssa-sprintf.c
@@ -2053,7 +2053,7 @@ get_string_length (tree str, gimple *stmt, unsigned eltsize,
       || !tree_fits_uhwi_p (lendata.maxlen))
     {
       fmtresult res;
-      res.nonstr = lendata.decl;
+      res.nonstr = lendata.nonstr;
       return res;
     }
 
@@ -2063,7 +2063,7 @@ get_string_length (tree str, gimple *stmt, unsigned eltsize,
       && lenmax <= tree_to_uhwi (lendata.maxlen))
     {
       fmtresult res;
-      res.nonstr = lendata.decl;
+      res.nonstr = lendata.nonstr;
       return res;
     }
 
@@ -2097,7 +2097,7 @@ get_string_length (tree str, gimple *stmt, unsigned eltsize,
     max = HOST_WIDE_INT_M1U;
 
   fmtresult res (min, max);
-  res.nonstr = lendata.decl;
+  res.nonstr = lendata.nonstr;
 
   /* Set RES.KNOWNRANGE to true if and only if all strings referenced
      by STR are known to be bounded (though not necessarily by their
diff --git a/gcc/testsuite/gcc.dg/Wstring-compare-4.c b/gcc/testsuite/gcc.dg/Wstring-compare-4.c
new file mode 100644
index 00000000000..dee8a316048
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstring-compare-4.c
@@ -0,0 +1,189 @@
+/* PR middle-end/98097 - Missing warning about strcmp (char[4], "BARZ")
+   { dg-do compile }
+   { dg-options "-O2 -Wall -Wstring-compare" } */
+
+extern int strcmp (const char*, const char*);
+
+struct SAx { char n, a[]; };
+struct SA0 { char n, a[0]; };
+struct SA1 { char n, a[1]; };
+struct SA2 { char n, a[2]; };
+struct SA5 { char n, a[5]; };
+
+void sink (void*, ...);
+#define T(x)   sink (0, (x))
+
+
+void test_ax (int i)
+{
+  extern char ax[];
+  const char *ps = i ? "" : "123";
+
+  T (!strcmp (ax, "1"));
+  T (!strcmp (ax, "12"));
+  T (!strcmp (ax, "123"));
+  T (!strcmp (ax, ps));
+}
+
+
+void test_SAx (int i, const struct SAx *px, const struct SA5 *p5)
+{
+  struct SAx ax;
+  sink (&ax);
+
+  T (!strcmp (ax.a, "1"));        // { dg-warning "\\\[-Wstringop-overread" }
+  T (!strcmp (ax.a, "12"));       // { dg-warning "\\\[-Wstringop-overread" }
+  T (!strcmp (ax.a, "123"));      // { dg-warning "\\\[-Wstringop-overread" }
+
+  const char *ps = i ? "" : "123";
+  T (!strcmp (ax.a, ps));         // { dg-warning "\\\[-Wstringop-overread" }
+
+  T (!strcmp (ax.a, px->a));      // { dg-warning "\\\[-Wstringop-overread" }
+  T (!strcmp (ax.a, p5->a));      // { dg-warning "\\\[-Wstringop-overread" }
+
+  T (!strcmp (px->a, "1"));
+  T (!strcmp (px->a, "12"));
+  T (!strcmp (px->a, "123"));
+
+  T (!strcmp (px->a, ps));
+  T (!strcmp (px->a, p5->a));
+}
+
+
+void test_a0 (int i)
+{
+  extern char a0[0];
+  const char *ps = i ? "" : "123";
+
+  T (!strcmp (a0, "1"));          // { dg-warning "\\\[-Wstringop-overread" }
+  T (!strcmp (a0, "12"));         // { dg-warning "\\\[-Wstringop-overread" }
+  T (!strcmp (a0, "123"));        // { dg-warning "\\\[-Wstringop-overread" }
+  T (!strcmp (a0, ps));           // { dg-warning "\\\[-Wstringop-overread" }
+}
+
+
+void test_SA0 (int i, const struct SA0 *p0, const struct SA5 *p5)
+{
+  struct SA0 a0;
+  sink (&a0);
+
+  T (!strcmp (a0.a, "1"));        // { dg-warning "\\\[-Wstringop-overread" }
+  T (!strcmp (a0.a, "12"));       // { dg-warning "\\\[-Wstringop-overread" }
+  T (!strcmp (a0.a, "123"));      // { dg-warning "\\\[-Wstringop-overread" }
+
+  const char *ps = i ? "" : "123";
+  T (!strcmp (a0.a, ps));         // { dg-warning "\\\[-Wstringop-overread" }
+
+  T (!strcmp (a0.a, p0->a));      // { dg-warning "\\\[-Wstringop-overread" }
+  T (!strcmp (a0.a, p5->a));      // { dg-warning "\\\[-Wstringop-overread" }
+
+  T (!strcmp (p0->a, "1"));
+  T (!strcmp (p0->a, "12"));
+  T (!strcmp (p0->a, "123"));
+
+  T (!strcmp (p0->a, ps));
+  T (!strcmp (p0->a, p5->a));
+}
+
+
+void test_a1 (int i)
+{
+  extern char a1[1];
+  const char *ps = i ? "" : "123";
+
+  T (!strcmp (a1, "1"));          // { dg-warning "\\\[-Wstring-compare" }
+  T (!strcmp (a1, "12"));         // { dg-warning "\\\[-Wstring-compare" }
+  T (!strcmp (a1, "123"));        // { dg-warning "\\\[-Wstring-compare" }
+  T (!strcmp (a1, ps));
+}
+
+
+void test_SA1 (int i, const struct SA1 *p1, const struct SA5 *p5)
+{
+  struct SA1 a1;
+  sink (&a1);
+
+  T (!strcmp (a1.a, "1"));        // { dg-warning "\\\[-Wstring-compare" }
+  T (!strcmp (a1.a, "12"));       // { dg-warning "\\\[-Wstring-compare" }
+  T (!strcmp (a1.a, "123"));      // { dg-warning "\\\[-Wstring-compare" }
+
+  /* This should arguably be diagnosed but probably only at a higher
+     warning level (if one were to be added).  */
+  const char *ps = i ? "" : "123";
+  T (!strcmp (a1.a, ps));
+
+  T (!strcmp (a1.a, p1->a));
+  T (!strcmp (a1.a, p5->a));
+
+  T (!strcmp (p1->a, "1"));
+  T (!strcmp (p1->a, "12"));
+  T (!strcmp (p1->a, "123"));
+
+  T (!strcmp (p1->a, ps));
+  T (!strcmp (p1->a, p5->a));
+}
+
+
+void test_a2 (int i)
+{
+  extern char a2[2];
+  const char *ps = i ? "" : "123";
+
+  T (!strcmp (a2, "1"));
+  T (!strcmp (a2, "12"));         // { dg-warning "\\\[-Wstring-compare" }
+  T (!strcmp (a2, "123"));        // { dg-warning "\\\[-Wstring-compare" }
+  T (!strcmp (a2, ps));
+}
+
+
+void test_SA2 (const struct SA2 *p2, const struct SA5 *p5)
+{
+  struct SA2 a2;
+  sink (&a2);
+
+  T (!strcmp (a2.a, "1"));
+  T (!strcmp (a2.a, "12"));       // { dg-warning "\\\[-Wstring-compare" }
+
+  T (!strcmp (a2.a, p2->a));
+  T (!strcmp (a2.a, p5->a));
+
+  T (!strcmp (p2->a, "1"));
+  T (!strcmp (p2->a, "12"));       // { dg-warning "\\\[-Wstring-compare" }
+
+  T (!strcmp (p2->a, p5->a));
+}
+
+
+void test_a5 (int i)
+{
+  extern char a5[5];
+  const char *ps = i ? "" : "123";
+
+  T (!strcmp (a5, "1"));
+  T (!strcmp (a5, "12"));
+  T (!strcmp (a5, "123"));
+  T (!strcmp (a5, "1234"));
+  T (!strcmp (a5, "12345"));      // { dg-warning "\\\[-Wstring-compare" }
+  T (!strcmp (a5, ps));
+}
+
+
+void test_SA5 (const struct SA5 *p5)
+{
+  struct SA5 a5;
+  sink (&a5);
+
+  T (!strcmp (a5.a, "1"));
+  T (!strcmp (a5.a, "12"));
+  T (!strcmp (a5.a, "123"));
+  T (!strcmp (a5.a, "1234"));
+  T (!strcmp (a5.a, "12345"));    // { dg-warning "\\\[-Wstring-compare" }
+
+  T (!strcmp (a5.a, p5->a));
+
+  T (!strcmp (p5->a, "1"));
+  T (!strcmp (p5->a, "12"));
+  T (!strcmp (p5->a, "123"));
+  T (!strcmp (p5->a, "1234"));
+  T (!strcmp (p5->a, "12345"));   // { dg-warning "\\\[-Wstring-compare" }
+}
diff --git a/gcc/testsuite/gcc.dg/Wstring-compare-5.c b/gcc/testsuite/gcc.dg/Wstring-compare-5.c
new file mode 100644
index 00000000000..14c0c02f93f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstring-compare-5.c
@@ -0,0 +1,213 @@
+/* PR middle-end/98097 - Missing warning about strcmp (char[4], "BARZ")
+   { dg-do compile }
+   { dg-options "-O2 -Wall -Wstring-compare" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+extern int strncmp (const char*, const char*, size_t);
+
+struct SAx { char n, a[]; };
+struct SA0 { char n, a[0]; };
+struct SA1 { char n, a[1]; };
+struct SA2 { char n, a[2]; };
+struct SA5 { char n, a[5]; };
+
+void sink (void*, ...);
+#define T(x)   sink (0, (x))
+
+
+void test_ax (int i)
+{
+  extern char ax[];
+  const char *ps = i ? "" : "123";
+
+  T (!strncmp (ax, "1", 1));
+  T (!strncmp (ax, "12", 2));
+  T (!strncmp (ax, "123", 3));
+  T (!strncmp (ax, "123", 9));
+  T (!strncmp (ax, ps, 9));
+}
+
+
+void test_SAx (int i, const struct SAx *px, const struct SA5 *p5)
+{
+  struct SAx ax;
+  sink (&ax);
+
+  T (!strncmp (ax.a, "12", 2));     // { dg-warning "\\\[-Wstringop-overread" "pr98553" { xfail *-*-* } }
+  T (!strncmp (ax.a, "123", 3));    // { dg-warning "\\\[-Wstringop-overread" "pr98553" { xfail *-*-* } }
+
+  const char *ps = i ? "" : "123";
+  T (!strncmp (ax.a, ps, 2));       // { dg-warning "\\\[-Wstringop-overread" }
+
+  T (!strncmp (ax.a, px->a, 2));    // { dg-warning "\\\[-Wstringop-overread" }
+  T (!strncmp (ax.a, p5->a, 3));    // { dg-warning "\\\[-Wstringop-overread" }
+
+  T (!strncmp (px->a, "1", 1));
+  T (!strncmp (px->a, "12", 2));
+  T (!strncmp (px->a, "123", 3));
+  T (!strncmp (px->a, "123", 9));
+
+  T (!strncmp (px->a, ps, 4));
+  T (!strncmp (px->a, p5->a, 5));
+  T (!strncmp (px->a, p5->a, 6));
+}
+
+
+void test_a0 (int i)
+{
+  extern char a0[0];
+  const char *ps = i ? "" : "123";
+
+  T (!strncmp (a0, "12", 2));       // { dg-warning "\\\[-Wstringop-overread" "pr98553" { xfail *-*-* } }
+  T (!strncmp (a0, "123", 3));      // { dg-warning "\\\[-Wstringop-overread" "pr98553" { xfail *-*-* } }
+  T (!strncmp (a0, ps, 3));         // { dg-warning "\\\[-Wstringop-overread" }
+}
+
+
+void test_SA0 (int i, const struct SA0 *p0, const struct SA5 *p5)
+{
+  struct SA0 a0;
+  sink (&a0);
+
+  T (!strncmp (a0.a, "12", 2));     // { dg-warning "\\\[-Wstringop-overread" "pr98553" { xfail *-*-* } }
+  T (!strncmp (a0.a, "123", 3));    // { dg-warning "\\\[-Wstringop-overread" "pr98553" { xfail *-*-* } }
+
+  const char *ps = i ? "" : "123";
+  T (!strncmp (a0.a, ps, 3));       // { dg-warning "\\\[-Wstringop-overread" }
+
+  T (!strncmp (a0.a, p0->a, 3));    // { dg-warning "\\\[-Wstringop-overread" }
+  T (!strncmp (a0.a, p5->a, 4));    // { dg-warning "\\\[-Wstringop-overread" }
+
+  T (!strncmp (p0->a, "1", 1));
+  T (!strncmp (p0->a, "12", 2));
+  T (!strncmp (p0->a, "123", 3));
+  T (!strncmp (p0->a, "123", 9));
+
+  T (!strncmp (p0->a, ps, 4));
+  T (!strncmp (p0->a, p5->a, 4));
+  // This should probably be diagnosed.
+  T (!strncmp (p0->a, p5->a, 9));
+}
+
+
+void test_a1 (int i)
+{
+  extern char a1[1];
+  const char *ps = i ? "" : "123";
+
+  T (!strncmp (a1, "12", 2));       // { dg-warning "\\\[-Wstring-compare" }
+  T (!strncmp (a1, "123", 3));      // { dg-warning "\\\[-Wstring-compare" }
+  T (!strncmp (a1, ps, 4));
+}
+
+
+void test_SA1 (int i, const struct SA1 *p1, const struct SA5 *p5)
+{
+  struct SA1 a1;
+  sink (&a1);
+
+  T (!strncmp (a1.a, "12", 2));     // { dg-warning "\\\[-Wstring-compare" }
+  T (!strncmp (a1.a, "123", 3));    // { dg-warning "\\\[-Wstring-compare" }
+
+  /* This should arguably be diagnosed but probably only at a higher
+     warning level (if one were to be added).  */
+  const char *ps = i ? "" : "123";
+  T (!strncmp (a1.a, ps, 3));
+
+  T (!strncmp (a1.a, p1->a, 3));
+  T (!strncmp (a1.a, p5->a, 3));
+  T (!strncmp (a1.a, p5->a, 7));    // { dg-warning "\\\[-Wstringop-overread" }
+
+  T (!strncmp (p1->a, "1", 1));
+  T (!strncmp (p1->a, "12", 2));
+  T (!strncmp (p1->a, "123", 3));
+
+  T (!strncmp (p1->a, ps, 3));
+  T (!strncmp (p1->a, p5->a, 4));
+  /* This should probably be diagnosed for a similar reason as is
+     strncmp (p1->a, p5->a, 7), even though p1->a's length is unknown
+     and unbounded.  */
+  T (!strncmp (p1->a, p5->a, 7));
+}
+
+
+void test_a2 (int i)
+{
+  extern char a2[2];
+  const char *ps = i ? "" : "123";
+
+  T (!strncmp (a2, "12", 2));
+  T (!strncmp (a2, "123", 3));      // { dg-warning "\\\[-Wstring-compare" }
+  T (!strncmp (a2, ps, 2));
+  T (!strncmp (a2, ps, 3));
+}
+
+
+void test_SA2 (const struct SA2 *p2, const struct SA5 *p5)
+{
+  struct SA2 a2;
+  sink (&a2);
+
+  T (!strncmp (a2.a, "12", 2));
+  T (!strncmp (a2.a, "123", 3));    // { dg-warning "\\\[-Wstring-compare" }
+
+  T (!strncmp (a2.a, p2->a, 2));
+  T (!strncmp (a2.a, p5->a, 4));
+
+  T (!strncmp (p2->a, "12", 2));
+  T (!strncmp (p2->a, "123", 3));   // { dg-warning "\\\[-Wstring-compare" }
+
+  T (!strncmp (p2->a, p5->a, 4));
+  T (!strncmp (p2->a, p5->a, 7));   // { dg-warning "\\\[-Wstringop-overread" }
+}
+
+
+void test_a5 (int i, const char *s)
+{
+  extern char a5[5];
+
+  T (!strncmp (a5, "12", 2));
+  T (!strncmp (a5, "12345", 5));
+  T (!strncmp (a5, "123456", 6));   // { dg-warning "\\\[-Wstring-compare" }
+
+  {
+    const char *ps = i ? "" : "123";
+    T (!strncmp (a5, ps, 3));
+    T (!strncmp (a5, ps, 4));
+    T (!strncmp (a5, ps, 5));
+  }
+
+  {
+    extern char ax[];
+    const char *ps = i ? ax : "123";
+
+    T (!strncmp (a5, ps, 3));
+    T (!strncmp (a5, ps, 4));
+    T (!strncmp (a5, ps, 5));
+  }
+
+  {
+    const char a[] = "12345";
+    const char *ps = i ? "abc" : s;
+    T (!strncmp (ps, a, sizeof a - 1));
+  }
+}
+
+
+void test_SA5 (const struct SA5 *p5)
+{
+  struct SA5 a5;
+  sink (&a5);
+
+  T (!strncmp (a5.a, "12", 2));
+  T (!strncmp (a5.a, "12345", 5));
+  T (!strncmp (a5.a, "123456", 6)); // { dg-warning "\\\[-Wstring-compare" }
+
+  T (!strncmp (a5.a, p5->a, 4));
+  T (!strncmp (a5.a, p5->a, 5));
+
+  T (!strncmp (p5->a, "12", 2));
+  T (!strncmp (p5->a, "12345", 5));
+  T (!strncmp (p5->a, "123456", 6));// { dg-warning "\\\[-Wstring-compare" }
+}
diff --git a/gcc/testsuite/gcc.dg/Wstring-compare.c b/gcc/testsuite/gcc.dg/Wstring-compare.c
index d1534bf7555..44a7c97b516 100644
--- a/gcc/testsuite/gcc.dg/Wstring-compare.c
+++ b/gcc/testsuite/gcc.dg/Wstring-compare.c
@@ -120,8 +120,7 @@ void strcmp_array_copy (void)
 
 void strcmp_member_array_lit (const struct S *p)
 {
-  // Not handled due to the fix for PR 92756.
-  T (p->a4, "1234");        // { dg-warning "length 4 and an array of size 4 " "pr92765" { xfail *-*-* } }
+  T (p->a4, "1234");        // { dg-warning "length 4 and an array of size 4 " "pr92765" }
 }
 
 
diff --git a/gcc/testsuite/gcc.dg/strcmpopt_10.c b/gcc/testsuite/gcc.dg/strcmpopt_10.c
index d7f94ac4d52..f094aed3930 100644
--- a/gcc/testsuite/gcc.dg/strcmpopt_10.c
+++ b/gcc/testsuite/gcc.dg/strcmpopt_10.c
@@ -117,7 +117,9 @@ void f2_memptr (void)
 
   struct A2 *p = (struct A2*)b.p;
 
-  if (__builtin_strncmp (p->a, "0123456789", 10) == 0)
+  /* The call triggers a warning based p->a's size but should not be
+     folded because p->a is a trailing array.  */
+  if (__builtin_strncmp (p->a, "0123456789", 10) == 0)    // { dg-warning "\\\[-Wstring-compare" }
     {
       extern void memptr_test (void);
       memptr_test ();
diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c
index 522b2d45b3a..b6b5b0af304 100644
--- a/gcc/tree-ssa-strlen.c
+++ b/gcc/tree-ssa-strlen.c
@@ -1053,10 +1053,10 @@ get_range_strlen_dynamic (tree src, gimple *stmt,
 		  if (get_range_strlen_dynamic (arg, phi, &argdata, visited,
 						rvals, pssa_def_max))
 		    {
-		      /* Set the DECL of an unterminated array this argument
+		      /* Set NONSTR of an unterminated array this argument
 			 refers to if one hasn't been found yet.  */
-		      if (!pdata->decl && argdata.decl)
-			pdata->decl = argdata.decl;
+		      if (!pdata->nonstr && argdata.nonstr)
+			pdata->nonstr = argdata.nonstr;
 
 		      if (!argdata.minlen
 			  || (integer_zerop (argdata.minlen)
@@ -1090,6 +1090,9 @@ get_range_strlen_dynamic (tree src, gimple *stmt,
 		    pdata->maxlen = build_all_ones_cst (size_type_node);
 		}
 
+	      /* Store the whole PHI as the DECL, overwriting any more
+		 "specific" DECL set above for any of the arguments.  */
+	      pdata->decl = src;
 	      return true;
 	    }
 	}
@@ -3880,20 +3883,21 @@ get_len_or_size (gimple *stmt, tree arg, int idx,
 		 unsigned HOST_WIDE_INT *size, bool *nulterm,
 		 range_query *rvals)
 {
-  /* Invalidate.  */
-  *size = HOST_WIDE_INT_M1U;
-
   if (idx < 0)
     {
       /* IDX is the inverted constant string length.  */
       lenrng[0] = ~idx;
       lenrng[1] = lenrng[0];
+      *size = lenrng[0] + 1;
       *nulterm = true;
       return true;
     }
 
-  /* Set so that both LEN and ~LEN are invalid lengths, i.e., maximum
-     possible length + 1.  */
+  /* Invalidate.  */
+  *size = HOST_WIDE_INT_M1U;
+
+  /* Set so that both LEN and ~LEN are invalid lengths, i.e., larger
+     than maximum possible length.  */
   lenrng[0] = lenrng[1] = HOST_WIDE_INT_MAX;
 
   if (strinfo *si = idx ? get_strinfo (idx) : NULL)
@@ -3944,23 +3948,41 @@ get_len_or_size (gimple *stmt, tree arg, int idx,
       unsigned HOST_WIDE_INT minlen = tree_to_uhwi (lendata.minlen);
       unsigned HOST_WIDE_INT maxlen = tree_to_uhwi (lendata.maxlen);
 
-      /* The longest string in this data model.  */
-      const unsigned HOST_WIDE_INT lenmax
-	= tree_to_uhwi (max_object_size ()) - 2;
-
       if (maxbound == HOST_WIDE_INT_M1U)
 	{
 	  lenrng[0] = minlen;
 	  lenrng[1] = maxlen;
 	  *nulterm = minlen == maxlen;
 	}
-      else if (maxlen < lenmax)
+      else
 	{
+	  /* The longest string in this data model.  */
+	  const unsigned HOST_WIDE_INT lenmax
+	    = tree_to_uhwi (max_object_size ()) - 2;
+
+	  if (lenmax <= maxlen)
+	    {
+	      if (!maxbound)
+		/* Bail for member arrays of size 1.  */
+		return false;
+
+	      if (lendata.decl && TREE_CODE (lendata.decl) == SSA_NAME)
+		/* Bail if ARG is or involves a PHI with at least one
+		   string of an unknown length.  */
+		return false;
+
+	      /* As a special case, make the range both invalid and inverted
+		 to let the caller avoid folding on the basis of array size
+		 while still enabling warnings.  */
+	      lenrng[1] = 0;
+	    }
+
+	  /* MAXBOUND is the length of the longest string in a PHI or
+	     the maximum possible length determined based on the size
+	     of the largest array.  Bump it up by 1 to reflect the size.  */
 	  *size = maxbound + 1;
 	  *nulterm = false;
 	}
-      else
-	return false;
 
       return true;
     }
@@ -3987,7 +4009,10 @@ get_len_or_size (gimple *stmt, tree arg, int idx,
    the array pointer to by the argument, set *PLEN and *PSIZE to
    the corresponding length (or its complement when the string is known
    to be at least as long and need not be nul-terminated) and size.
-   Otherwise return null.  */
+   If the result were to be zero based on the string lengths constrained
+   to the sizes of the member character arrays they are stored in return
+   void_node instead to let the caller issue a warning but avoid folding
+   the result of the call.  Otherwise return null.  */
 
 static tree
 strxcmp_eqz_result (gimple *stmt, tree arg1, int idx1, tree arg2, int idx2,
@@ -4003,7 +4028,24 @@ strxcmp_eqz_result (gimple *stmt, tree arg1, int idx1, tree arg2, int idx2,
       || !get_len_or_size (stmt, arg2, idx2, len2rng, &siz2, &nul2, rvals))
     return NULL_TREE;
 
-  /* BOUND is set to HWI_M1U for strcmp and less to strncmp, and LENiRNG
+  /* LENiRNG is set to the inverted maximum range below for trailing
+     arrays where folding on the basis of member array size is disabled
+     but warning is enabled the same as for non-member arrays.  */
+  tree mos_def_zero = integer_zero_node;
+  if (len1rng[0] == HOST_WIDE_INT_MAX && len1rng[1] == 0)
+    {
+      /* Restore the upper bound to make the range valid.  */
+      len1rng[1] = HOST_WIDE_INT_MAX;
+      mos_def_zero = void_node;
+    }
+
+  if (len2rng[0] == HOST_WIDE_INT_MAX && len2rng[1] == 0)
+    {
+      len2rng[1] = HOST_WIDE_INT_MAX;
+      mos_def_zero = void_node;
+    }
+
+  /* BOUND is set to HWI_M1U for strcmp and less for strncmp, and LENiRNG
      to HWI_MAX when invalid.  Adjust the length of each string to consider
      to be no more than BOUND.  */
   if (len1rng[0] < HOST_WIDE_INT_MAX && len1rng[0] > bound)
@@ -4033,7 +4075,7 @@ strxcmp_eqz_result (gimple *stmt, tree arg1, int idx1, tree arg2, int idx2,
 	 nul-terminated or to the complement of its minimum length
 	 otherwise,  */
       len[1] = nul2 ? len2rng[0] : ~len2rng[0];
-      return integer_zero_node;
+      return mos_def_zero;
     }
 
   if (len2rng[0] == HOST_WIDE_INT_MAX
@@ -4044,7 +4086,7 @@ strxcmp_eqz_result (gimple *stmt, tree arg1, int idx1, tree arg2, int idx2,
       *psize = siz2;
       len[0] = nul1 ? len1rng[0] : ~len1rng[0];
       len[1] = len2rng[0];
-      return integer_zero_node;
+      return mos_def_zero;
     }
 
   /* The strings are also definitely unequal when their lengths are unequal
@@ -4061,7 +4103,7 @@ strxcmp_eqz_result (gimple *stmt, tree arg1, int idx1, tree arg2, int idx2,
 
       len[0] = len1rng[0];
       len[1] = len2rng[0];
-      return integer_zero_node;
+      return mos_def_zero;
     }
 
   /* The string lengths may be equal or unequal.  Even when equal and
@@ -4197,6 +4239,12 @@ handle_builtin_string_cmp (gimple_stmt_iterator *gsi, range_query *rvals)
     if (tree eqz = strxcmp_eqz_result (stmt, arg1, idx1, arg2, idx2, bound,
 				       len, &siz, rvals))
       {
+	if (eqz == void_node)
+	{
+	  maybe_warn_pointless_strcmp (stmt, bound, len, siz);
+	  return false;
+	}
+
 	if (integer_zerop (eqz))
 	  {
 	    maybe_warn_pointless_strcmp (stmt, bound, len, siz);
