PR c/77531 - __attribute__((alloc_size(1,2))) could also warn on multiplication overflow
PR c/78284 - warn on malloc with very large arguments

gcc/c-family/ChangeLog:

	PR c/77531
	PR c/78284
	* c.opt (-Walloc-zero, -Walloc-larger-than): New options.

gcc/testsuite/ChangeLog:

	PR c/77531
	PR c/78284
	* gcc.dg/attr-alloc_size-3.c: New test.

gcc/ChangeLog:

	PR c/77531
	PR c/78284
	* calls.c (alloc_max_size): New function.
	(maybe_warn_alloc_args_overflow): Same.
	(initialize_argument_information): Diagnose overflow in functions
	declared with attaribute alloc_size.
	* doc/invoke.texi (Warning Options): Document -Walloc-zero and
	-Walloc-larger-than.

diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 213353b..72c1e14 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -299,6 +299,15 @@ Walloca
 C ObjC C++ ObjC++ Var(warn_alloca) Warning
 Warn on any use of alloca.
 
+Walloc-larger-than=
+C ObjC C++ ObjC++ Var(warn_alloc_limit) Warning Joined
+-Walloc-larger-than=<bytes> Warn for calls to allocation functions that attempt
+to allocate objects larger than the specified number of bytes.
+
+Walloc-zero
+C ObjC C++ ObjC++ Var(warn_alloc_zero) Warning EnabledBy(Wextra)
+-Walloc-zero Warn for calls to allocation functions that specify zero bytes.
+
 Walloca-larger-than=
 C ObjC C++ ObjC++ Var(warn_alloca_limit) Warning Joined RejectNegative UInteger
 -Walloca-larger-than=<number> Warn on unbounded uses of
diff --git a/gcc/calls.c b/gcc/calls.c
index c916e07..dfeb5fe 100644
--- a/gcc/calls.c
+++ b/gcc/calls.c
@@ -48,6 +48,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "dbgcnt.h"
 #include "rtl-iter.h"
 #include "tree-chkp.h"
+#include "tree-vrp.h"
+#include "tree-ssanames.h"
 #include "rtl-chkp.h"
 
 
@@ -1181,6 +1183,192 @@ store_unaligned_arguments_into_pseudos (struct arg_data *args, int num_actuals)
       }
 }
 
+static tree alloc_object_size_limit;
+
+/* Initialize ALLOC_OBJECT_SIZE_LIMIT based on the -Walloc-larger-than=limit
+   setting if the option is specified, or to the maximum object size if it
+   is not.  Return the initialized value.  */
+
+static tree
+alloc_max_size (void)
+{
+  if (!alloc_object_size_limit)
+    {
+      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_limit ? strtoull (warn_alloc_limit, &end, 10) : 0;
+
+      if (limit && !errno)
+	{
+	  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);
+	}
+    }
+  return alloc_object_size_limit;
+}
+
+/* Diagnose a call EXP to function FN decorated with attribute alloc_size
+   whose argument numbers given by IDX with values given by ARGS exceed
+   the maximum object size or cause an unsigned oveflow (wrapping) when
+   multiplied.  When ARGS[0] is null the function does nothing.  ARGS[1]
+   may be null for functions like malloc, and non-null for those like
+   calloc that are decorated with a two-argument attribute alloc_size.  */
+
+static void
+maybe_warn_alloc_args_overflow (tree fn, tree exp, tree args[2], int idx[2])
+{
+  if (!args[0])
+    return;
+
+  bool warned = false;
+
+  if (!args[1])
+    args[1] = size_one_node;
+
+  bool range[2] = { false, false };
+
+  /* Determine the minimum value of the argument.  */
+  for (unsigned i = 0; i != 2; ++i)
+    {
+      if (TREE_CODE (args[i]) == SSA_NAME)
+	{
+	  tree type = TREE_TYPE (args[i]);
+	  wide_int zero = build_int_cst (type, 0);
+	  signop sgn = TYPE_SIGN (type);
+
+	  wide_int min, max;
+	  value_range_type range_type = get_range_info (args[i], &min, &max);
+	  if (range_type == VR_RANGE)
+	    {
+	      if (wi::lt_p (min, zero, sgn) && wi::ge_p (max, zero, sgn))
+		continue;
+
+	      args[i] = wide_int_to_tree (type, min);
+	    }
+	  else if (range_type == VR_ANTI_RANGE)
+	    {
+	      if (wi::lt_p (min - 1, zero, sgn) && wi::ge_p (max + 1, zero, sgn))
+		continue;
+	      args[i] = wide_int_to_tree (type, max + 1);
+	    }
+	  else
+	    args[i] = size_one_node;
+
+	  range[i] = true;
+
+	  if (integer_zerop (args[i]))
+	    args[i] = size_one_node;
+	}
+    }
+
+  tree maxobjsize = alloc_max_size ();
+
+  location_t loc = EXPR_LOCATION (exp);
+
+  if (tree_fits_uhwi_p (args[0]) && tree_fits_uhwi_p (args[1])
+      && !integer_zerop (args[0]) && !integer_zerop (args[1])
+      && !integer_onep (args[0]) && !integer_onep (args[1]))
+    {
+      /* Check for overflow in the product of a function decorated with
+	 attribute alloc_size (X, Y).  */
+      unsigned szprec = TYPE_PRECISION (size_type_node);
+      wide_int x = wi::to_wide (args[0], szprec);
+      wide_int y = wi::to_wide (args[1], szprec);
+
+      bool vflow;
+      wide_int prod = wi::umul (x, y, &vflow);
+
+      if (vflow)
+	warned = warning_at (loc, OPT_Walloc_larger_than_,
+			     "product of arguments %i and %i results "
+			     "in unsigned overflow: %<%E * %E%>",
+			     idx[0] + 1, idx[1] + 1, args[0], args[1]);
+      else if (wi::ltu_p (wi::to_wide (maxobjsize, szprec), prod))
+	warned = warning_at (loc, OPT_Walloc_larger_than_,
+			     "product of arguments %i and %i exceeds "
+			     "maximum object size %E: %<%E * %E%>",
+			     idx[0] + 1, idx[1] + 1,
+			     maxobjsize, args[0], args[1]);
+    }
+  else
+    {
+      /* This is either a function decorated with attribute alloc_size (X),
+	 or one that takes two allocation arguments like realloc and one
+	 of the arguments is equal to 0, or to 1, or it value is unknown.  */
+      for (unsigned i = 0; i != 2; ++i)
+	{
+	  if (tree_fits_uhwi_p (args[i]))
+	    if (tree_int_cst_lt (maxobjsize, args[i]))
+	      {
+		warned = warning_at (loc, OPT_Walloc_larger_than_,
+				     "argument %i value %qE exceeds maximum "
+				     "object size %E",
+				     idx[i] + 1, args[i], maxobjsize);
+		break;
+	      }
+	  if (!range[i])
+	    {
+	      if (integer_zerop (args[i]))
+		{
+		  warned = warning_at (loc, OPT_Walloc_zero,
+				       "argument %i value is zero",
+				       idx[i] + 1);
+		  break;
+		}
+	      else if (!tree_expr_nonnegative_p (args[i]))
+		{
+		  warned = warning_at (loc, OPT_Walloc_larger_than_,
+				       "argument %i value %qE is negative",
+				       idx[i] + 1, args[i]);
+		  break;
+		}
+	    }
+	}
+    }
+
+  if (warned)
+    inform (DECL_SOURCE_LOCATION (fn),
+	    "in a call to allocation function %qD declared here", fn);
+}
+
 /* Issue an error if CALL_EXPR was flagged as requiring
    tall-call optimization.  */
 
@@ -1280,6 +1468,7 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
 	    j--;
 	  }
       }
+
     argpos = 0;
     FOR_EACH_CALL_EXPR_ARG (arg, iter, exp)
       {
@@ -1349,6 +1538,7 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
 	  }
 	else
 	  args[j].tree_value = arg;
+
 	j--;
 	argpos++;
       }
@@ -1359,6 +1549,24 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
 
   bitmap_obstack_release (NULL);
 
+  /* Extract attribute alloc_size and if set, store the indices of
+     the corresponding arguments in ALLOC_IDX, and then the actual
+     argument(s) at those indices in ALLOC_ARGS.  */
+  int alloc_idx[2] = { -1, -1 };
+  if (tree alloc_size
+      = (fndecl ? lookup_attribute ("alloc_size",
+				    TYPE_ATTRIBUTES (TREE_TYPE (fndecl)))
+	 : NULL_TREE))
+    {
+      tree args = TREE_VALUE (alloc_size);
+      alloc_idx[0] = TREE_INT_CST_LOW (TREE_VALUE (args)) - 1;
+      if (TREE_CHAIN (args))
+	alloc_idx[1] = TREE_INT_CST_LOW (TREE_VALUE (TREE_CHAIN (args))) - 1;
+    }
+
+  /* Array for up to the two attribute alloc_size arguments.  */
+  tree alloc_args[] = { NULL_TREE, NULL_TREE };
+
   /* I counts args in order (to be) pushed; ARGPOS counts in order written.  */
   for (argpos = 0; argpos < num_actuals; i--, argpos++)
     {
@@ -1595,7 +1803,17 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
 
       targetm.calls.function_arg_advance (args_so_far, TYPE_MODE (type),
 					  type, argpos < n_named_args);
+
+      /* Store argument values for functions decorated with attribute
+	 alloc_size.  */
+      if (argpos == alloc_idx[0])
+	alloc_args[0] = args[i].tree_value;
+      else if (argpos == alloc_idx[1])
+	alloc_args[1] = args[i].tree_value;
     }
+
+  /* Check the arguments of functions decorated with attribute alloc_size. */
+  maybe_warn_alloc_args_overflow (fndecl, exp, alloc_args, alloc_idx);
 }
 
 /* Update ARGS_SIZE to contain the total size for the argument block.
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 76b8540..2ff0b65 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -256,6 +256,7 @@ Objective-C and Objective-C++ Dialects}.
 @gccoptlist{-fsyntax-only  -fmax-errors=@var{n}  -Wpedantic @gol
 -pedantic-errors @gol
 -w  -Wextra  -Wall  -Waddress  -Waggregate-return  @gol
+-Walloc-zero -Walloc-larger-than=@var{n}
 -Walloca -Walloca-larger-than=@var{n} @gol
 -Wno-aggressive-loop-optimizations -Warray-bounds -Warray-bounds=@var{n} @gol
 -Wno-attributes -Wbool-compare -Wbool-operation @gol
@@ -4990,6 +4991,17 @@ annotations.
 Warn about overriding virtual functions that are not marked with the override
 keyword.
 
+@item -Walloc-zero
+@opindex Wno-alloc-zero
+@opindex Walloc-zero
+Warn about calls to allocation functions that specify zero bytes.  This option
+is enabled with @option{-Wextra}
+
+@item -Walloc-larger-than=@var{n}
+Warn about calls to functions decorated with attribute @code{alloc_size}
+that attempt to allocate objects larger than the specified number of bytes,
+or where the computation of the size would overflow.  @xref{Function Attributes}.
+
 @item -Walloca
 @opindex Wno-alloca
 @opindex Walloca
diff --git a/gcc/testsuite/gcc.dg/attr-alloc_size-3.c b/gcc/testsuite/gcc.dg/attr-alloc_size-3.c
new file mode 100644
index 0000000..adf9333
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/attr-alloc_size-3.c
@@ -0,0 +1,101 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -Wall -Walloc-zero" } */
+
+#define SCHAR_MAX  __SCHAR_MAX__
+#define SCHAR_MIN  (-SCHAR_MAX - 1)
+#define UCHAR_MAX  (SCHAR_MAX * 2 + 1)
+
+#define SHRT_MAX   __SHRT_MAX__
+#define SHRT_MIN   (-SHRT_MAX - 1)
+#define USHRT_MAX  (SHRT_MAX * 2 + 1)
+
+#define INT_MAX    __INT_MAX__
+#define INT_MIN    (-INT_MAX - 1)
+#define UINT_MAX   (INT_MAX * 2 + 1)
+
+#define LONG_MAX   __LONG_MAX__
+#define LONG_MIN   (-LONG_MAX - 1L)
+#define ULONG_MAX  (LONG_MAX * 2 + 1)
+
+#define LLONG_MAX  __LLONG_MAX__
+#define LLONG_MIN  (-LLONG_MAX - 1LL)
+#define ULLONG_MAX (ULLONG_MAX * 2 + 1)
+
+#define ALLOC_SIZE(...) __attribute__ ((alloc_size (__VA_ARGS__)))
+
+void* f_uchar_1 (unsigned char) ALLOC_SIZE (1);
+void* f_schar_1 (signed char) ALLOC_SIZE (1);
+
+void* f_ushrt_1 (unsigned short) ALLOC_SIZE (1);
+void* f_shrt_1 (signed short) ALLOC_SIZE (1);
+
+void* f_uint_1 (unsigned int) ALLOC_SIZE (1);
+void* f_sint_1 (signed int) ALLOC_SIZE (1);
+
+void* f_ulong_1 (unsigned long) ALLOC_SIZE (1);
+void* f_slong_1 (signed long) ALLOC_SIZE (1);
+
+void* f_ullong_1 (unsigned long long) ALLOC_SIZE (1);
+void* f_sllong_1 (signed long long) ALLOC_SIZE (1);
+
+void sink (void*);
+
+void
+test_uchar (unsigned char n)
+{
+  sink (f_uchar_1 (UCHAR_MAX));
+  sink (f_uchar_1 (1));
+  sink (f_uchar_1 (0));  /* { dg-warning "argument 1 value is zero" } */
+  sink (f_uchar_1 (n));
+}
+
+void
+test_schar (signed char n)
+{
+  sink (f_schar_1 (SCHAR_MAX));
+  sink (f_schar_1 (SCHAR_MIN));   /* { dg-warning "argument 1 value .-\[0-9\]+. is negative" } */
+  sink (f_schar_1 (-1));          /* { dg-warning "argument 1 value .-1. is negative" } */
+  sink (f_schar_1 (0));
+  sink (f_schar_1 (1));
+  sink (f_schar_1 (n));
+}
+
+void
+test_ushrt (unsigned short n)
+{
+  sink (f_ushrt_1 (USHRT_MAX));
+  sink (f_ushrt_1 (1));
+  sink (f_ushrt_1 (0));  /* { dg-warning "argument 1 value is zero" } */
+  sink (f_ushrt_1 (n));
+}
+
+void
+test_sshrt (signed short n)
+{
+  sink (f_shrt_1 (SHRT_MAX));
+  sink (f_shrt_1 (SHRT_MIN));   /* { dg-warning "argument 1 value .-\[0-9\]+. is negative" } */
+  sink (f_shrt_1 (-1));         /* { dg-warning "argument 1 value .-1. is negative" } */
+  sink (f_shrt_1 (0));          /* { dg-warning "argument 1 value is zero" } */
+  sink (f_shrt_1 (1));
+  sink (f_shrt_1 (n));
+}
+
+void
+test_uint (unsigned int n)
+{
+  sink (f_uint_1 (UINT_MAX));
+  sink (f_uint_1 (1));
+  sink (f_uint_1 (0));  /* { dg-warning "argument 1 value is zero" } */
+  sink (f_uint_1 (n));
+}
+
+void
+test_sint (signed short n)
+{
+  sink (f_int_1 (INT_MAX));
+  sink (f_int_1 (INT_MIN));   /* { dg-warning "argument 1 value .-\[0-9\]+. is negative" } */
+  sink (f_int_1 (-1));         /* { dg-warning "argument 1 value .-1. is negative" } */
+  sink (f_int_1 (0));          /* { dg-warning "argument 1 value is zero" } */
+  sink (f_int_1 (1));
+  sink (f_int_1 (n));
+}
