PR middle-end/102200 - ICE on a min of a decl and pointer in a loop

gcc/ChangeLog:

	PR middle-end/102200
	* pointer-query.cc (access_ref::inform_access): Handle MIN/MAX_EXPR.
	(handle_min_max_size): Change argument.  Store original SSA_NAME for
	operands to potentially distinct (sub)objects.
	(compute_objsize_r): Adjust call to the above.

gcc/testsuite/ChangeLog:

	PR middle-end/102200
	* gcc.dg/Wstringop-overflow-62.c: Adjust text of an expected note.
	* gcc.dg/Warray-bounds-89.c: New test.
	* gcc.dg/Wstringop-overflow-74.c: New test.
	* gcc.dg/Wstringop-overflow-75.c: New test.
	* gcc.dg/Wstringop-overflow-76.c: New test.

diff --git a/gcc/pointer-query.cc b/gcc/pointer-query.cc
index 4ad28796e57..83b1f0fc866 100644
--- a/gcc/pointer-query.cc
+++ b/gcc/pointer-query.cc
@@ -1087,6 +1087,34 @@ access_ref::inform_access (access_mode mode) const
       else if (gimple_nop_p (stmt))
 	/* Handle DECL_PARM below.  */
 	ref = SSA_NAME_VAR (ref);
+      else if (is_gimple_assign (stmt)
+	       && (gimple_assign_rhs_code (stmt) == MIN_EXPR
+		   || gimple_assign_rhs_code (stmt) == MAX_EXPR))
+	{
+	  /* MIN or MAX_EXPR here implies a reference to a known object
+	     and either an unknown or distinct one (the latter being
+	     the result of an invalid relational expression).  Determine
+	     the identity of the former and point to it in the note.
+	     TODO: Consider merging with PHI handling.  */
+	  access_ref arg_ref[2];
+	  tree arg = gimple_assign_rhs1 (stmt);
+	  compute_objsize (arg, /* ostype = */ 1 , &arg_ref[0]);
+	  arg = gimple_assign_rhs2 (stmt);
+	  compute_objsize (arg, /* ostype = */ 1 , &arg_ref[1]);
+
+	  /* Use the argument that references a known object with more
+	     space remaining.  */
+	  const bool idx
+	    = (!arg_ref[0].ref || !arg_ref[0].base0
+	       || (arg_ref[0].base0 && arg_ref[1].base0
+		   && (arg_ref[0].size_remaining ()
+		       < arg_ref[1].size_remaining ())));
+
+	  arg_ref[idx].offrng[0] = offrng[0];
+	  arg_ref[idx].offrng[1] = offrng[1];
+	  arg_ref[idx].inform_access (mode);
+	  return;
+	}
     }
 
   if (DECL_P (ref))
@@ -1463,15 +1491,18 @@ pointer_query::dump (FILE *dump_file, bool contents /* = false */)
 }
 
 /* A helper of compute_objsize_r() to determine the size from an assignment
-   statement STMT with the RHS of either MIN_EXPR or MAX_EXPR.  */
+   statement STMT with the RHS of either MIN_EXPR or MAX_EXPR.  On success
+   set PREF->REF to the operand with more or less space remaining,
+   respectively, if both refer to the same (sub)object, or to PTR if they
+   might not, and return true.  Otherwise, if the identity of neither
+   operand can be determined, return false.  */
 
 static bool
-handle_min_max_size (gimple *stmt, int ostype, access_ref *pref,
+handle_min_max_size (tree ptr, int ostype, access_ref *pref,
 		     ssa_name_limit_t &snlim, pointer_query *qry)
 {
-  tree_code code = gimple_assign_rhs_code (stmt);
-
-  tree ptr = gimple_assign_rhs1 (stmt);
+  const gimple *stmt = SSA_NAME_DEF_STMT (ptr);
+  const tree_code code = gimple_assign_rhs_code (stmt);
 
   /* In a valid MAX_/MIN_EXPR both operands must refer to the same array.
      Determine the size/offset of each and use the one with more or less
@@ -1479,7 +1510,8 @@ handle_min_max_size (gimple *stmt, int ostype, access_ref *pref,
      determined from the other instead, adjusted up or down as appropriate
      for the expression.  */
   access_ref aref[2] = { *pref, *pref };
-  if (!compute_objsize_r (ptr, ostype, &aref[0], snlim, qry))
+  tree arg1 = gimple_assign_rhs1 (stmt);
+  if (!compute_objsize_r (arg1, ostype, &aref[0], snlim, qry))
     {
       aref[0].base0 = false;
       aref[0].offrng[0] = aref[0].offrng[1] = 0;
@@ -1487,8 +1519,8 @@ handle_min_max_size (gimple *stmt, int ostype, access_ref *pref,
       aref[0].set_max_size_range ();
     }
 
-  ptr = gimple_assign_rhs2 (stmt);
-  if (!compute_objsize_r (ptr, ostype, &aref[1], snlim, qry))
+  tree arg2 = gimple_assign_rhs2 (stmt);
+  if (!compute_objsize_r (arg2, ostype, &aref[1], snlim, qry))
     {
       aref[1].base0 = false;
       aref[1].offrng[0] = aref[1].offrng[1] = 0;
@@ -1517,6 +1549,13 @@ handle_min_max_size (gimple *stmt, int ostype, access_ref *pref,
 	    *pref = aref[i1];
 	  else
 	    *pref = aref[i0];
+
+	  if (aref[i0].ref != aref[i1].ref)
+	    /* If the operands don't refer to the same (sub)object set
+	       PREF->REF to the SSA_NAME from which STMT was obtained
+	       so that both can be identified in a diagnostic.  */
+	    pref->ref = ptr;
+
 	  return true;
 	}
 
@@ -1537,6 +1576,10 @@ handle_min_max_size (gimple *stmt, int ostype, access_ref *pref,
       pref->offrng[0] = aref[i0].offrng[0];
       pref->offrng[1] = aref[i0].offrng[1];
     }
+
+  /* Replace PTR->REF with the SSA_NAME to indicate the expression
+     might not refer to the same (sub)object.  */
+  pref->ref = ptr;
   return true;
 }
 
@@ -2009,8 +2052,9 @@ compute_objsize_r (tree ptr, int ostype, access_ref *pref,
 
       if (code == MAX_EXPR || code == MIN_EXPR)
 	{
-	  if (!handle_min_max_size (stmt, ostype, pref, snlim, qry))
+	  if (!handle_min_max_size (ptr, ostype, pref, snlim, qry))
 	    return false;
+
 	  qry->put_ref (ptr, *pref);
 	  return true;
 	}
diff --git a/gcc/testsuite/gcc.dg/Warray-bounds-89.c b/gcc/testsuite/gcc.dg/Warray-bounds-89.c
new file mode 100644
index 00000000000..2604f65e6d5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Warray-bounds-89.c
@@ -0,0 +1,139 @@
+/* Verify warnings and notes for MIN_EXPRs involving either pointers
+   to distinct objects or one to a known object and the other to
+   an unknown one.  The relational expressions are strictly invalid
+   but that should be diagnosed by a separate warning.
+   { dg-do compile }
+   { dg-options "-O2 -Warray-bounds -Wno-stringop-overflow" } */
+
+/* Verify the note points to the larger of the two objects and mentions
+   the offset into it (alhough the offset would ideally be a part of
+   the warning).  */
+extern char a3[3];
+extern char a5[5];  // { dg-message "at offset 5 into object 'a5' of size 5" "note" }
+
+void min_a3_a5 (int i)
+{
+  char *p = a3 + i;
+  char *q = a5 + i;
+
+  /* The relational expression below is invalid and should be diagnosed
+     by its own warning independently of -Warray-bounds.  */
+  char *d = p < q ? p : q;
+
+  d[4] = 0;
+
+  /* Verify the type in the warning corresponds to the larger of the two
+     objects.  */
+  d[5] = 0;         // { dg-warning "subscript 5 is outside array bounds of 'char\\\[5]'" }
+}
+
+
+// Same as above but with the larger array as the first MIN_EXPR operand.
+extern char b4[4];
+extern char b6[6];  // { dg-message "at offset 6 into object 'b6' of size 6" "note" }
+
+void min_b6_b4 (int i)
+{
+  char *p = b6 + i;
+  char *q = b4 + i;
+  char *d = p < q ? p : q;
+
+  d[5] = 0;
+  d[6] = 0;         // { dg-warning "subscript 6 is outside array bounds of 'char\\\[6]'" }
+}
+
+
+/* Same as above but with the first MIN_EXPR operand pointing to an unknown
+   object.  */
+extern char c7[7];  // { dg-message "at offset 7 into object 'c7' of size 7" "note" }
+
+void min_p_c7 (char *p, int i)
+{
+  char *q = c7 + i;
+  char *d = p < q ? p : q;
+
+  d[6] = 0;
+  d[7] = 0;         // { dg-warning "subscript 7 is outside array bounds of 'char\\\[7]'" }
+}
+
+
+/* Same as above but with the second MIN_EXPR operand pointing to an unknown
+   object.  */
+extern char d8[8];  // { dg-message "at offset 8 into object 'd8' of size 8" "note" }
+
+void min_d8_p (char *q, int i)
+{
+  char *p = d8 + i;
+  char *d = p < q ? p : q;
+
+  d[7] = 0;
+  d[8] = 0;         // { dg-warning "subscript 8 is outside array bounds of 'char\\\[8]'" }
+}
+
+
+/* The following are diagnosed by -Wstringop-overflow but, as a result
+   of PR 101374, not by -Warray-bounds.  */
+
+struct A3_5
+{
+  char a3[3];
+  char a5[5];  // { dg-message "at offset 5 into object 'a5' of size 5" "note" { xfail *-*-* } }
+};
+
+void min_A3_A5 (int i, struct A3_5 *pa3_5)
+{
+  char *p = pa3_5->a3 + i;
+  char *q = pa3_5->a5 + i;
+
+  char *d = p < q ? p : q;
+
+  // d[4] = 0;
+  d[5] = 0;         // { dg-warning "subscript 5 is outside array bounds of 'char\\\[5]'" "pr??????" { xfail *-*-* } }
+}
+
+
+struct B4_B6
+{
+  char b4[4];
+  char b6[6];       // { dg-message "at offset 6 into object 'b6' of size 6" "note" { xfail *-*-* } }
+};
+
+void min_B6_B4 (int i, struct B4_B6 *pb4_b6)
+{
+  char *p = pb4_b6->b6 + i;
+  char *q = pb4_b6->b4 + i;
+  char *d = p < q ? p : q;
+
+  d[5] = 0;
+  d[6] = 0;         // { dg-warning "subscript 6 is outside array bounds of 'char\\\[6]'" "pr??????" { xfail *-*-* } }
+}
+
+
+struct C7
+{
+  char c7[7];       // { dg-message "at offset 7 into object 'c7' of size 7" "note" { xfail *-*-* } }
+};
+
+void min_p_C7 (char *p, int i, struct C7 *pc7)
+{
+  char *q = pc7->c7 + i;
+  char *d = p < q ? p : q;
+
+  d[6] = 0;
+  d[7] = 0;         // { dg-warning "subscript 7 is outside array bounds of 'char\\\[7]'" "pr??????" { xfail *-*-* } }
+}
+
+
+struct D8
+{
+  char d8[8];       // { dg-message "at offset 8 into object 'd8' of size 8" "note" { xfail *-*-* } }
+};
+
+void min_D8_p (char *q, int i, struct D8 *pd8)
+{
+  char *p = pd8->d8 + i;
+  char *d = p < q ? p : q;
+
+  d[7] = 0;
+  d[8] = 0;         // { dg-warning "subscript 8 is outside array bounds of 'char\\\[8]'" "pr??????" { xfail *-*-* } }
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-62.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-62.c
index 318d9bd1f94..4b6d1ab83c5 100644
--- a/gcc/testsuite/gcc.dg/Wstringop-overflow-62.c
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-62.c
@@ -117,7 +117,7 @@ void test_min (void)
   {
     /* Exercise a pointer pointing to a known object plus constant offset
        with one pointing to an unknown object.  */
-    char a6[6];               // { dg-message ": destination object 'a6'" "note" }
+    char a6[6];               // { dg-message "(at offset 1 into )?destination object 'a6'" "note" }
     char *p1 = ptr;
     char *p2 = a6 + 1;
     char *q = MIN (p1, p2);
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-74.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-74.c
new file mode 100644
index 00000000000..bacec964d36
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-74.c
@@ -0,0 +1,22 @@
+/* PR middle-end/102200 - ICE on a min of a decl and pointer in a loop
+   { dg-do compile }
+   { dg-options "-O2 -Wall" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+extern char a[], n;
+
+void f (void)
+{
+  char *p = a;
+  size_t end = 1;
+
+  while (n)
+    {
+      if (p < (char*)end)
+        *p = ';';
+
+      if (p > (char*)&end)
+        p = (char*)&end;
+    }
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-75.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-75.c
new file mode 100644
index 00000000000..2ebadc649a5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-75.c
@@ -0,0 +1,133 @@
+/* Verify warnings and notes for MIN_EXPRs involving either pointers
+   to distinct objects or one to a known object and the other to
+   an unknown one.  The relational expressions are strictly invalid
+   but that should be diagnosed by a separate warning.
+   { dg-do compile }
+   { dg-options "-O2 -Wno-array-bounds" } */
+
+/* Verify the note points to the larger of the two objects and mentions
+   the offset into it (alhough the offset would ideally be a part of
+   the warning).  */
+extern char a3[3];
+extern char a5[5];  // { dg-message "at offset 5 into destination object 'a5' of size 5" "note" }
+
+void min_a3_a5 (int i)
+{
+  char *p = a3 + i;
+  char *q = a5 + i;
+
+  /* The relational expression below is invalid and should be diagnosed
+     by its own warning independently of -Wstringop-overflow.  */
+  char *d = p < q ? p : q;
+
+  d[4] = 0;
+  d[5] = 0;         // { dg-warning "writing 1 byte into a region of size 0" }
+}
+
+
+// Same as above but with the larger array as the first MIN_EXPR operand.
+extern char b4[4];
+extern char b6[6];  // { dg-message "at offset 6 into destination object 'b6' of size 6" "note" }
+
+void min_b6_b4 (int i)
+{
+  char *p = b6 + i;
+  char *q = b4 + i;
+  char *d = p < q ? p : q;
+
+  d[5] = 0;
+  d[6] = 0;         // { dg-warning "writing 1 byte into a region of size 0" }
+}
+
+
+/* Same as above but with the first MIN_EXPR operand pointing to an unknown
+   object.  */
+extern char c7[7];  // { dg-message "at offset 7 into destination object 'c7' of size 7" "note" }
+
+void min_p_c7 (char *p, int i)
+{
+  char *q = c7 + i;
+  char *d = p < q ? p : q;
+
+  d[6] = 0;
+  d[7] = 0;         // { dg-warning "writing 1 byte into a region of size 0" }
+}
+
+
+/* Same as above but with the second MIN_EXPR operand pointing to an unknown
+   object.  */
+extern char d8[8];  // { dg-message "at offset 8 into destination object 'd8' of size 8" "note" }
+
+void min_d8_p (char *q, int i)
+{
+  char *p = d8 + i;
+  char *d = p < q ? p : q;
+
+  d[7] = 0;
+  d[8] = 0;         // { dg-warning "writing 1 byte into a region of size 0" }
+}
+
+
+struct A3_5
+{
+  char a3[3];
+  char a5[5];  // { dg-message "at offset 5 into destination object 'a5' of size 5" "note" }
+};
+
+void min_A3_A5 (int i, struct A3_5 *pa3_5)
+{
+  char *p = pa3_5->a3 + i;
+  char *q = pa3_5->a5 + i;
+
+  char *d = p < q ? p : q;
+
+  // d[4] = 0;
+  d[5] = 0;         // { dg-warning "writing 1 byte into a region of size 0" }
+}
+
+
+struct B4_B6
+{
+  char b4[4];
+  char b6[6];       // { dg-message "at offset 6 into destination object 'b6' of size 6" "note" }
+};
+
+void min_B6_B4 (int i, struct B4_B6 *pb4_b6)
+{
+  char *p = pb4_b6->b6 + i;
+  char *q = pb4_b6->b4 + i;
+  char *d = p < q ? p : q;
+
+  d[5] = 0;
+  d[6] = 0;         // { dg-warning "writing 1 byte into a region of size 0" }
+}
+
+
+struct C7
+{
+  char c7[7];       // { dg-message "at offset 7 into destination object 'c7' of size 7" "note" }
+};
+
+void min_p_C7 (char *p, int i, struct C7 *pc7)
+{
+  char *q = pc7->c7 + i;
+  char *d = p < q ? p : q;
+
+  d[6] = 0;
+  d[7] = 0;         // { dg-warning "writing 1 byte into a region of size 0" }
+}
+
+
+struct D8
+{
+  char d8[8];       // { dg-message "at offset 8 into destination object 'd8' of size 8" "note" }
+};
+
+void min_D8_p (char *q, int i, struct D8 *pd8)
+{
+  char *p = pd8->d8 + i;
+  char *d = p < q ? p : q;
+
+  d[7] = 0;
+  d[8] = 0;         // { dg-warning "writing 1 byte into a region of size 0" }
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-76.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-76.c
new file mode 100644
index 00000000000..18191a1aa5e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-76.c
@@ -0,0 +1,148 @@
+/* Verify warnings and notes for MAX_EXPRs involving either pointers
+   to distinct objects or one to a known object and the other to
+   an unknown one.  Unlike for the same object, for unrelated objects
+   the expected warnings and notes are the same as for MIN_EXPR: when
+   the order of the objects in the address space cannot be determined
+   the larger of them is assumed to be used.  (This is different for
+   distinct struct members where the order is given.)
+   The relational expressions are strictly invalid but that should be
+   diagnosed by a separate warning.
+   { dg-do compile }
+   { dg-options "-O2 -Wno-array-bounds" } */
+
+#define MAX(p, q) ((p) > (q) ? (p) : (q))
+
+/* Verify that even for MAX_EXPR and like for MIN_EXPR, the note points
+   to the larger of the two objects and mentions the offset into it
+   (although the offset might be better included in the warning).  */
+extern char a3[3];
+extern char a5[5];  // { dg-message "at offset 5 into destination object 'a5' of size 5" "note" }
+
+void max_a3_a5 (int i)
+{
+  char *p = a3 + i;
+  char *q = a5 + i;
+
+  /* The relational expression below is invalid and should be diagnosed
+     by its own warning independently of -Wstringop-overflow.  */
+  char *d = MAX (p, q);
+
+  d[2] = 0;
+  d[3] = 0;
+  d[4] = 0;
+  d[5] = 0;         // { dg-warning "writing 1 byte into a region of size 0" }
+}
+
+
+// Same as above but with the larger array as the first MAX_EXPR operand.
+extern char b4[4];
+extern char b6[6];  // { dg-message "at offset 6 into destination object 'b6' of size 6" "note" }
+
+void max_b6_b4 (int i)
+{
+  char *p = b6 + i;
+  char *q = b4 + i;
+  char *d = MAX (p, q);
+
+  d[3] = 0;
+  d[4] = 0;
+  d[5] = 0;
+  d[6] = 0;         // { dg-warning "writing 1 byte into a region of size 0" }
+}
+
+
+/* Same as above but with the first MAX_EXPR operand pointing to an unknown
+   object.  */
+extern char c7[7];  // { dg-message "at offset 7 into destination object 'c7' of size 7" "note" }
+
+void max_p_c7 (char *p, int i)
+{
+  char *q = c7 + i;
+  char *d = MAX (p, q);
+
+  d[6] = 0;
+  d[7] = 0;         // { dg-warning "writing 1 byte into a region of size 0" }
+}
+
+
+/* Same as above but with the second MIN_EXPR operand pointing to an unknown
+   object.  */
+extern char d8[8];  // { dg-message "at offset 8 into destination object 'd8' of size 8" "note" }
+
+void max_d8_p (char *q, int i)
+{
+  char *p = d8 + i;
+  char *d = MAX (p, q);
+
+  d[7] = 0;
+  d[8] = 0;         // { dg-warning "writing 1 byte into a region of size 0" }
+}
+
+
+struct A3_5
+{
+  char a3[3];  // { dg-message "at offset 3 into destination object 'a3' of size 3" "pr??????" { xfail *-*-* } }
+  char a5[5];  // { dg-message "at offset 5 into destination object 'a5' of size 5" "note" }
+};
+
+void max_A3_A5 (int i, struct A3_5 *pa3_5)
+{
+  char *p = pa3_5->a3 + i;
+  char *q = pa3_5->a5 + i;
+
+  char *d = MAX (p, q);
+
+  d[2] = 0;
+  d[3] = 0;         // { dg-warning "writing 1 byte into a region of size 0" "pr??????" { xfail *-*-* } }
+  d[4] = 0;
+  d[5] = 0;         // { dg-warning "writing 1 byte into a region of size 0" }
+}
+
+
+struct B4_B6
+{
+  char b4[4];
+  char b6[6];       // { dg-message "at offset 6 into destination object 'b6' of size 6" "note" }
+};
+
+void max_B6_B4 (int i, struct B4_B6 *pb4_b6)
+{
+  char *p = pb4_b6->b6 + i;
+  char *q = pb4_b6->b4 + i;
+  char *d = MAX (p, q);
+
+  d[3] = 0;
+  d[4] = 0;
+  d[5] = 0;
+  d[6] = 0;         // { dg-warning "writing 1 byte into a region of size 0" }
+}
+
+
+struct C7
+{
+  char c7[7];       // { dg-message "at offset 7 into destination object 'c7' of size 7" "note" }
+};
+
+void max_p_C7 (char *p, int i, struct C7 *pc7)
+{
+  char *q = pc7->c7 + i;
+  char *d = MAX (p, q);
+
+  d[6] = 0;
+  d[7] = 0;         // { dg-warning "writing 1 byte into a region of size 0" }
+}
+
+
+struct D8
+{
+  char d8[8];       // { dg-message "at offset 8 into destination object 'd8' of size 8" "note" }
+};
+
+void max_D8_p (char *q, int i, struct D8 *pd8)
+{
+  char *p = pd8->d8 + i;
+  char *d = MAX (p, q);
+
+  d[7] = 0;
+  d[8] = 0;         // { dg-warning "writing 1 byte into a region of size 0" }
+}
