PR tree-optimization/78775 - [7 Regression] ICE in maybe_warn_alloc_args_overflow

gcc/ChangeLog:

	PR tree-optimization/78775
	* builtins.c (get_size_range): Move...
	* calls.c: ...to here.
	(alloc_max_size): Accept zero argument.
	(operand_signed_p): Remove.
	(maybe_warn_alloc_args_overflow): Call get_size_range.
	* calls.h (get_size_range): Declare.

gcc/testsuite/ChangeLog:

	PR tree-optimization/78775
	* gcc.dg/attr-alloc_size-4.c: Add test cases.
	* gcc.dg/pr78775.c: New test.
	* gcc.dg/pr78973-2.c: New test.
	* gcc.dg/pr78973.c: New test.

diff --git a/gcc/builtins.c b/gcc/builtins.c
index 5b76dfd..bf68e31 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -3031,42 +3031,6 @@ expand_builtin_memcpy_args (tree dest, tree src, tree len, rtx target, tree exp)
   return dest_addr;
 }
 
-/* Fill the 2-element RANGE array with the minimum and maximum values
-   EXP is known to have and return true, otherwise null and return
-   false.  */
-
-static bool
-get_size_range (tree exp, tree range[2])
-{
-  if (tree_fits_uhwi_p (exp))
-    {
-      range[0] = range[1] = exp;
-      return true;
-    }
-
-  if (TREE_CODE (exp) == SSA_NAME)
-    {
-      wide_int min, max;
-      enum value_range_type range_type = get_range_info (exp, &min, &max);
-
-      if (range_type == VR_RANGE)
-	{
-	  /* Interpret the bound in the variable's type.  */
-	  range[0] = wide_int_to_tree (TREE_TYPE (exp), min);
-	  range[1] = wide_int_to_tree (TREE_TYPE (exp), max);
-	  return true;
-	}
-      else if (range_type == VR_ANTI_RANGE)
-	{
-	  /* FIXME: Handle anti-ranges.  */
-	}
-    }
-
-  range[0] = NULL_TREE;
-  range[1] = NULL_TREE;
-  return false;
-}
-
 /* Try to verify that the sizes and lengths of the arguments to a string
    manipulation function given by EXP are within valid bounds and that
    the operation does not lead to buffer overflow.  Arguments other than
diff --git a/gcc/calls.c b/gcc/calls.c
index b7bbec5..c0d60bb 100644
--- a/gcc/calls.c
+++ b/gcc/calls.c
@@ -1197,92 +1197,189 @@ alloc_max_size (void)
     {
       alloc_object_size_limit = TYPE_MAX_VALUE (ssizetype);
 
-      unsigned HOST_WIDE_INT unit = 1;
-
-      char *end;
-      errno = 0;
-      unsigned HOST_WIDE_INT limit
-	= warn_alloc_size_limit ? strtoull (warn_alloc_size_limit, &end, 10) : 0;
-
-      if (limit && !errno)
+      if (warn_alloc_size_limit)
 	{
-	  if (end && *end)
+	  char *end = NULL;
+	  errno = 0;
+	  unsigned HOST_WIDE_INT unit = 1;
+	  unsigned HOST_WIDE_INT limit
+	    = strtoull (warn_alloc_size_limit, &end, 10);
+
+	  if (!errno)
 	    {
-	      /* Numeric option arguments are at most INT_MAX.  Make it
-		 possible to specify a larger value by accepting common
-		 suffixes.  */
-	      if (!strcmp (end, "kB"))
-		unit = 1000;
-	      else if (!strcasecmp (end, "KiB") || strcmp (end, "KB"))
-		unit = 1024;
-	      else if (!strcmp (end, "MB"))
-		unit = 1000LU * 1000;
-	      else if (!strcasecmp (end, "MiB"))
-		unit = 1024LU * 1024;
-	      else if (!strcasecmp (end, "GB"))
-		unit = 1000LU * 1000 * 1000;
-	      else if (!strcasecmp (end, "GiB"))
-		unit = 1024LU * 1024 * 1024;
-	      else if (!strcasecmp (end, "TB"))
-		unit = 1000LU * 1000 * 1000 * 1000;
-	      else if (!strcasecmp (end, "TiB"))
-		unit = 1024LU * 1024 * 1024 * 1024;
-	      else if (!strcasecmp (end, "PB"))
-		unit = 1000LU * 1000 * 1000 * 1000 * 1000;
-	      else if (!strcasecmp (end, "PiB"))
-		unit = 1024LU * 1024 * 1024 * 1024 * 1024;
-	      else if (!strcasecmp (end, "EB"))
-		unit = 1000LU * 1000 * 1000 * 1000 * 1000 * 1000;
-	      else if (!strcasecmp (end, "EiB"))
-		unit = 1024LU * 1024 * 1024 * 1024 * 1024 * 1024;
-	      else
-		unit = 0;
-	    }
+	      if (end && *end)
+		{
+		  /* Numeric option arguments are at most INT_MAX.  Make it
+		     possible to specify a larger value by accepting common
+		     suffixes.  */
+		  if (!strcmp (end, "kB"))
+		    unit = 1000;
+		  else if (!strcasecmp (end, "KiB") || strcmp (end, "KB"))
+		    unit = 1024;
+		  else if (!strcmp (end, "MB"))
+		    unit = 1000LU * 1000;
+		  else if (!strcasecmp (end, "MiB"))
+		    unit = 1024LU * 1024;
+		  else if (!strcasecmp (end, "GB"))
+		    unit = 1000LU * 1000 * 1000;
+		  else if (!strcasecmp (end, "GiB"))
+		    unit = 1024LU * 1024 * 1024;
+		  else if (!strcasecmp (end, "TB"))
+		    unit = 1000LU * 1000 * 1000 * 1000;
+		  else if (!strcasecmp (end, "TiB"))
+		    unit = 1024LU * 1024 * 1024 * 1024;
+		  else if (!strcasecmp (end, "PB"))
+		    unit = 1000LU * 1000 * 1000 * 1000 * 1000;
+		  else if (!strcasecmp (end, "PiB"))
+		    unit = 1024LU * 1024 * 1024 * 1024 * 1024;
+		  else if (!strcasecmp (end, "EB"))
+		    unit = 1000LU * 1000 * 1000 * 1000 * 1000 * 1000;
+		  else if (!strcasecmp (end, "EiB"))
+		    unit = 1024LU * 1024 * 1024 * 1024 * 1024 * 1024;
+		  else
+		    unit = 0;
+		}
 
-	  if (unit)
-	    alloc_object_size_limit = build_int_cst (ssizetype, limit * unit);
+	      if (unit)
+		alloc_object_size_limit
+		  = build_int_cst (ssizetype, limit * unit);
+	    }
 	}
     }
   return alloc_object_size_limit;
 }
 
-/* Return true if the type of OP is signed, looking through any casts
-   to an unsigned type.  */
+/* Return true when EXP's range can be determined and set RANGE[] to it
+   after adjusting it if necessary to make EXP a valid size argument to
+   an allocation function declared with attribute alloc_size (whose
+   argument may be signed), or to a string manipulation function like
+   memset.  SIGNED_P is initially set to -1 and is used internally by
+   the function and should not be explicitly passed in by callers.  */
 
-static bool
-operand_signed_p (tree op)
+bool
+get_size_range (tree exp, tree range[2], int signed_p /* = -1 */)
 {
-  if (TREE_CODE (op) == SSA_NAME)
+  if (tree_fits_uhwi_p (exp))
+    {
+      /* EXP is a constant.  */
+      range[0] = range[1] = exp;
+      return true;
+    }
+
+  if (TREE_CODE (exp) != SSA_NAME)
+    {
+      /* No range information available. */
+      range[0] = NULL_TREE;
+      range[1] = NULL_TREE;
+      return false;
+    }
+
+  wide_int min, max;
+  enum value_range_type range_type = get_range_info (exp, &min, &max);
+
+  tree exptype = TREE_TYPE (exp);
+  unsigned expprec = TYPE_PRECISION (exptype);
+  wide_int wzero = wi::zero (expprec);
+
+  /* Set SIGNED_P once (will be used by recursive calls).  */
+  if (signed_p < 0)
+    signed_p = !TYPE_UNSIGNED (exptype);
+
+  if (range_type == VR_VARYING)
     {
-      gimple *def = SSA_NAME_DEF_STMT (op);
-      if (is_gimple_assign (def))
+      /* No range information available. */
+      range[0] = NULL_TREE;
+      range[1] = NULL_TREE;
+      return false;
+    }
+
+  if (range_type == VR_ANTI_RANGE)
+    {
+      if (TYPE_UNSIGNED (exptype) || wi::les_p (wzero, min))
 	{
-	  /* In an assignment involving a cast, ignore the type
-	     of the cast and consider the type of its  operand.  */
-	  tree_code code = gimple_assign_rhs_code (def);
-	  if (code == NOP_EXPR)
-	    op = gimple_assign_rhs1 (def);
+	  /* EXP is either unsigned or strictly not in an unsigned
+	     range.  Set the result range using zero as the lower
+	     bound if that's still a range or the upper bound if
+	     using zero would result in [0, 0].  */
+	  if (wi::lts_p (wzero, min - 1))
+	    {
+	      max = min - 1;
+	      min = wzero;
+	    }
+	  else
+	    {
+	      min = max + 1;
+	      max = wide_int (TYPE_MAX_VALUE (exptype));
+	    }
 	}
-      else if (gimple_code (def) == GIMPLE_PHI)
+      else if (wi::les_p (max, wzero))
 	{
-	  /* In a phi, a constant argument may be unsigned even
-	     if in the source it's signed and negative.  Ignore
-	     those and consider the result of a phi signed if
-	     all its non-constant operands are.  */
-	  unsigned nargs = gimple_phi_num_args (def);
-	  for (unsigned i = 0; i != nargs; ++i)
+	  /* EXP is not in a strictly negative range.  That means
+	     it must be in some (not necessarily strictly) positive
+	     range which includes zero.  Since in signed to unsigned
+	     conversions negative values end up converted to large
+	     positive values, and otherwise they are not valid sizes,
+	     the resulting range is in both cases [0, TYPE_MAX].  */
+	  min = wzero;
+	  max = wide_int (TYPE_MAX_VALUE (exptype));
+	}
+      else if (signed_p)
+	{
+	  /* EXP is not in a negative-positive range and no conversion
+	     is being performed.  That means EXP is either negative,
+	     or greater than max.  Since negative sizes are invalid
+	     make the range [MAX + 1, TYPE_MAX].  */
+	  min = max + 1;
+	  max = wide_int (TYPE_MAX_VALUE (exptype));
+	}
+      else
+	{
+	  /* EXP is not in negative-positive range and it's being
+	     converted to an unsigned.  That means the negative
+	     lower bound is inverted and the resulting range is
+	     either [~(MIN - 1), MAX + 1] or [MAX + 1, ~(MIN - 1)],
+	     whichever results in the lower bound being less than
+	     the upper bound.  */
+	  --min;
+	  ++max;
+
+	  /* For a signed to unsigned conversion invert
+	     the minimum.  */
+	  min = ~min;
+
+	  if (wi::ltu_p (max, min))
 	    {
-	      tree op = gimple_phi_arg_def (def, i);
-	      if (TREE_CODE (op) != INTEGER_CST
-		  && !operand_signed_p (op))
-		return false;
+	      wide_int tmp = max;
+	      max = min;
+	      min = tmp;
 	    }
-
-	  return true;
 	}
     }
 
-  return !TYPE_UNSIGNED (TREE_TYPE (op));
+  if (TYPE_UNSIGNED (exptype) || !wi::neg_p (min) || signed_p)
+    {
+      /* EXP is either unsigned or in a non-negative range,
+	 or the result is not being converted to unsigned.
+	 Use its range as is.  */
+      range[0] = wide_int_to_tree (exptype, min);
+      range[1] = wide_int_to_tree (exptype, max);
+    }
+  else if (wi::neg_p (min) && wi::les_p (max, wzero))
+    {
+      /* EXP is in a strictly negative range (or zero) and is
+	 being converted to unsigned.  Also use its range as is.  */
+      range[0] = wide_int_to_tree (exptype, min);
+      range[1] = wide_int_to_tree (exptype, max);
+    }
+  else
+    {
+      /* Otherwise, clear the lower bound if it's negative and
+	 the upper bound is positive.  */
+      range[0] = integer_zero_node;
+      range[1] = wide_int_to_tree (exptype, max);
+    }
+
+  return true;
 }
 
 /* Diagnose a call EXP to function FN decorated with attribute alloc_size
@@ -1316,8 +1413,8 @@ maybe_warn_alloc_args_overflow (tree fn, tree exp, tree args[2], int idx[2])
 	  if (tree_int_cst_lt (args[i], integer_zero_node))
 	    {
 	      warned = warning_at (loc, OPT_Walloc_size_larger_than_,
-				   "argument %i value %qE is negative",
-				   idx[i] + 1, args[i]);
+				   "%Kargument %i value %qE is negative",
+				   exp, idx[i] + 1, args[i]);
 	    }
 	  else if (integer_zerop (args[i]))
 	    {
@@ -1334,8 +1431,8 @@ maybe_warn_alloc_args_overflow (tree fn, tree exp, tree args[2], int idx[2])
 		      && !lookup_attribute ("returns_nonnull",
 					    TYPE_ATTRIBUTES (TREE_TYPE (fn)))))
 		warned = warning_at (loc, OPT_Walloc_zero,
-				     "argument %i value is zero",
-				     idx[i] + 1);
+				     "%Kargument %i value is zero",
+				     exp, idx[i] + 1);
 	    }
 	  else if (tree_int_cst_lt (maxobjsize, args[i]))
 	    {
@@ -1351,79 +1448,31 @@ maybe_warn_alloc_args_overflow (tree fn, tree exp, tree args[2], int idx[2])
 		continue;
 
 	      warned = warning_at (loc, OPT_Walloc_size_larger_than_,
-				   "argument %i value %qE exceeds "
+				   "%Kargument %i value %qE exceeds "
 				   "maximum object size %E",
-				   idx[i] + 1, args[i], maxobjsize);
+				   exp, idx[i] + 1, args[i], maxobjsize);
 	    }
 	}
-      else if (TREE_CODE (args[i]) == SSA_NAME)
+      else if (TREE_CODE (args[i]) == SSA_NAME
+	       && get_size_range (args[i], argrange[i]))
 	{
-	  tree type = TREE_TYPE (args[i]);
-
-	  wide_int min, max;
-	  value_range_type range_type = get_range_info (args[i], &min, &max);
-	  if (range_type == VR_RANGE)
-	    {
-	      argrange[i][0] = wide_int_to_tree (type, min);
-	      argrange[i][1] = wide_int_to_tree (type, max);
-	    }
-	  else if (range_type == VR_ANTI_RANGE)
-	    {
-	      /* For an anti-range, if the type of the formal argument
-		 is unsigned and the bounds of the range are of opposite
-		 signs when interpreted as signed, check to see if the
-		 type of the actual argument is signed.  If so, the lower
-		 bound must be taken to be zero (rather than a large
-		 positive value corresonding to the actual lower bound
-		 interpreted as unsigned) and there is nothing else that
-		 can be inferred from it.  */
-	      --min;
-	      ++max;
-	      wide_int zero = wi::uhwi (0, TYPE_PRECISION (type));
-	      if (TYPE_UNSIGNED (type)
-		  && wi::lts_p (zero, min) && wi::lts_p (max, zero)
-		  && operand_signed_p (args[i]))
-		continue;
-
-	      argrange[i][0] = wide_int_to_tree (type, max);
-	      argrange[i][1] = wide_int_to_tree (type, min);
-
-	      /* Verify that the anti-range doesn't make all arguments
-		 invalid (treat the anti-range ~[0, 0] as invalid).  */
-	      if (tree_int_cst_lt (maxobjsize, argrange[i][0])
-		  && tree_int_cst_le (argrange[i][1], integer_zero_node))
-		{
-		  warned
-		    = warning_at (loc, OPT_Walloc_size_larger_than_,
-				  (TYPE_UNSIGNED (type)
-				   ? G_("argument %i range [%E, %E] exceeds "
-					"maximum object size %E")
-				   : G_("argument %i range [%E, %E] is both "
-					"negative and exceeds maximum object "
-					"size %E")),
-				  idx[i] + 1, argrange[i][0],
-				  argrange[i][1], maxobjsize);
-		}
-	      continue;
-	    }
-	  else
-	    continue;
-
 	  /* Verify that the argument's range is not negative (including
 	     upper bound of zero).  */
 	  if (tree_int_cst_lt (argrange[i][0], integer_zero_node)
 	      && tree_int_cst_le (argrange[i][1], integer_zero_node))
 	    {
 	      warned = warning_at (loc, OPT_Walloc_size_larger_than_,
-				   "argument %i range [%E, %E] is negative",
-				   idx[i] + 1, argrange[i][0], argrange[i][1]);
+				   "%Kargument %i range [%E, %E] is negative",
+				   exp, idx[i] + 1,
+				   argrange[i][0], argrange[i][1]);
 	    }
 	  else if (tree_int_cst_lt (maxobjsize, argrange[i][0]))
 	    {
 	      warned = warning_at (loc, OPT_Walloc_size_larger_than_,
-				   "argument %i range [%E, %E] exceeds "
+				   "%Kargument %i range [%E, %E] exceeds "
 				   "maximum object size %E",
-				   idx[i] + 1, argrange[i][0], argrange[i][1],
+				   exp, idx[i] + 1,
+				   argrange[i][0], argrange[i][1],
 				   maxobjsize);
 	    }
 	}
@@ -1450,15 +1499,15 @@ maybe_warn_alloc_args_overflow (tree fn, tree exp, tree args[2], int idx[2])
 
       if (vflow)
 	warned = warning_at (loc, OPT_Walloc_size_larger_than_,
-			     "product %<%E * %E%> of arguments %i and %i "
+			     "%Kproduct %<%E * %E%> of arguments %i and %i "
 			     "exceeds %<SIZE_MAX%>",
-			     argrange[0][0], argrange[1][0],
+			     exp, argrange[0][0], argrange[1][0],
 			     idx[0] + 1, idx[1] + 1);
       else if (wi::ltu_p (wi::to_wide (maxobjsize, szprec), prod))
 	warned = warning_at (loc, OPT_Walloc_size_larger_than_,
-			     "product %<%E * %E%> of arguments %i and %i "
+			     "%Kproduct %<%E * %E%> of arguments %i and %i "
 			     "exceeds maximum object size %E",
-			     argrange[0][0], argrange[1][0],
+			     exp, argrange[0][0], argrange[1][0],
 			     idx[0] + 1, idx[1] + 1,
 			     maxobjsize);
 
diff --git a/gcc/calls.h b/gcc/calls.h
index e87fbda..ed4b75b 100644
--- a/gcc/calls.h
+++ b/gcc/calls.h
@@ -38,5 +38,6 @@ extern bool pass_by_reference (CUMULATIVE_ARGS *, machine_mode,
 extern bool reference_callee_copied (CUMULATIVE_ARGS *, machine_mode,
 				     tree, bool);
 extern void maybe_warn_alloc_args_overflow (tree, tree, tree[2], int[2]);
+extern bool get_size_range (tree, tree[2], int = -1);
 
 #endif // GCC_CALLS_H
diff --git a/gcc/testsuite/gcc.dg/attr-alloc_size-4.c b/gcc/testsuite/gcc.dg/attr-alloc_size-4.c
index 6b70a85..5ce593e 100644
--- a/gcc/testsuite/gcc.dg/attr-alloc_size-4.c
+++ b/gcc/testsuite/gcc.dg/attr-alloc_size-4.c
@@ -128,15 +128,22 @@ test_int_range (int n)
 
   sink (f_int_1 (SR (min, 1234)));
   sink (f_int_1 (SR (-2, -1)));   /* { dg-warning "argument 1 range \\\[-2, -1\\\] is negative" } */
+
   sink (f_int_1 (SR (1235, 2345)));  /* { dg-warning "argument 1 range \\\[1235, 2345\\\] exceeds maximum object size 1234" } */
   sink (f_int_1 (SR (max - 1, max)));   /* { dg-warning "argument 1 range \\\[\[0-9\]+, \[0-9\]+\\\] exceeds maximum object size 1234" } */
 
   sink (f_int_1 (SAR (-1, 1)));
   sink (f_int_1 (SAR (-2, 12)));
   sink (f_int_1 (SAR (-3, 123)));
-  sink (f_int_1 (SAR (-4, 1234)));   /* { dg-warning "argument 1 range \\\[1235, -5\\\] is both negative and exceeds maximum object size 1234" } */
+  sink (f_int_1 (SAR (-4, 1234)));   /* { dg-warning "argument 1 range \\\[1235, \[0-9\]+\\\] exceeds maximum object size 1234" } */
   sink (f_int_1 (SAR (min + 1, 1233)));
-  sink (f_int_1 (SAR (min + 2, 1235)));   /* { dg-warning "argument 1 range \\\[1236, -\[0-9\]+\\\] is both negative and exceeds maximum object size 1234" } */
+  sink (f_int_1 (SAR (min + 2, 1235)));   /* { dg-warning "argument 1 range \\\[1236, \[0-9\]+\\\] exceeds maximum object size 1234" } */
+  sink (f_int_1 (SAR (0, max)));   /* { dg-warning "argument 1 range \\\[-\[0-9\]*, -1\\\] is negative" } */
+  /* The range below includes zero which would be diagnosed by
+     -Walloc-size-zero but since all other values are negative it
+     is diagnosed by -Walloc-size-larger-than.  */
+  sink (f_int_1 (SAR (1, max)));   /* { dg-warning "argument 1 range \\\[-\[0-9\]*, 0\\\] is negative" } */
+  sink (f_int_1 (SAR (2, max)));
 }
 
 void
diff --git a/gcc/testsuite/gcc.dg/pr78775.c b/gcc/testsuite/gcc.dg/pr78775.c
new file mode 100644
index 0000000..120c252
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/pr78775.c
@@ -0,0 +1,19 @@
+/* PR c/78775 - [7 Regression] ICE in maybe_warn_alloc_args_overflow
+   { dg-do compile }
+   { dg-options "-O2" } */
+
+int a, b, *c;
+
+int main (void)
+{
+  unsigned long d = 0;
+  while (1)
+    {
+      switch (b)
+      case 'S':
+	d = a;
+      c = __builtin_malloc (d);
+    }
+
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.dg/pr78973-2.c b/gcc/testsuite/gcc.dg/pr78973-2.c
new file mode 100644
index 0000000..dadec16
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/pr78973-2.c
@@ -0,0 +1,25 @@
+/* PR c/78973 - warning: ‘memcpy’: specified size exceeds maximum object
+   size [-Wstringop-overflow=]
+
+   This is a companion test for the bug above that verifies that the correct
+   range of the int variable is detected.
+
+   { dg-do compile }
+   { dg-require-effective-target int32plus }
+   { dg-options "-O2 -Walloc-size-larger-than=4" }  */
+
+void *p;
+
+void f (int n)
+{
+  if (n <= 4)
+    p = __builtin_malloc (n);
+  /* { dg-warning "argument 1 range \\\[5, 2147483647\\\] exceeds maximum object size 4" "ilp32" { xfail { ! lp64 } } .-1 } */
+}
+
+void g (unsigned n)
+{
+  if (n < 5)
+    n = 5;
+  f (n);
+}
diff --git a/gcc/testsuite/gcc.dg/pr78973.c b/gcc/testsuite/gcc.dg/pr78973.c
new file mode 100644
index 0000000..a6195f0
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/pr78973.c
@@ -0,0 +1,20 @@
+/* PR c/78973 - warning: ‘memcpy’: specified size exceeds maximum object size
+
+   Test case for what was initially thought to be a false positive but after
+   deeper investigation turned out to be a true positive.
+
+   { dg-do compile }
+   { dg-options "-O2 -Wall" }  */
+
+void f (void *p, int n)
+{
+  if (n <= 4)
+    __builtin_memset (p, 0, n);   /* { dg-warning "exceeds maximum object size" } */
+}
+
+void g (void *d, unsigned n)
+{
+  if (n < 5)
+    n = 5;
+  f (d, n);
+}
