public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH 0/5] add checking of function array parameters (PR 50584)
@ 2020-07-29  1:13 Martin Sebor
  2020-07-29  1:16 ` [PATCH 1/5] infrastructure to detect out-of-bounds accesses to array parameters Martin Sebor
                   ` (4 more replies)
  0 siblings, 5 replies; 33+ messages in thread
From: Martin Sebor @ 2020-07-29  1:13 UTC (permalink / raw)
  To: gcc-patches; +Cc: Martin Sebor

The C99 T[static N] form of declaring function arguments means
the argument must point to an object large enough to fit at least
N elements of T.  Diagnosing calls where the argument is smaller
helps detect buffer overflow.  Similarly, so does diagnosing
accesses by such functions to parameters beyond the N'th element.

Declaring function arguments as VLAs with variable bounds given
by other arguments to the same function (besides other expressions)
is another opportunity to validate accesses.

These standard features are similar to GCC 10 attribute access,
except that in GCC 10 they are independent and there's no checking
for out of bounds accesses.

Although the standard C requirements on [static N] arrays and VLA
arguments are exceedingly loose, letting programs mix and match 
different forms in distinct declarations of the same function with
impunity (or at least without any requirement to diagnose it),
doing so results in mismatches of constraints the different forms
express(*).  For instance, the following are considered compatible
declarations of the same functions in C, even though they obviously
express different requirements:

   void f (int[static 3);
   void f (int[static 7);

   void g (int nelts, int,       int[nelts]);
   void g (int,       int count, int[count]);

To both enable the detection of out-of-bounds accesses involving
these arguments and help reconcile the conflicting constraints
of redeclarations like those above, the attached patch integrates
arrays and VLA function arguments with attribute access.  It has
these effects:

1) For the first declaration of a function that specifies either
    one or more array with constant bounds or VLAs as arguments it
    adds an implicit attribute access specification describing all
    such arguments.
2) Function redeclarations are checked mismtatches in the form of
    their array/VLA/pointer arguments and their bounds and warnings
    are issued when any are found. (-Warray-parameter for ordinary
    arrays and -Wvla-paramater for VLAs).
3) Declarations that mix explicit attribute access with VLAs are
    checked for compatibility and warnings are again issued for
    mismatches.
4) Enables the same checking as for the explicit attribute access
    also for functions taking arrays or VLAs.
5) In the definitions of functions declared with attribute access
    it checks for accesses to their arguments and issues
    -Warray-bounds warnings when they're out of bounds.

C++ has no [static] or VLA arguments, so the initial implementation
only adds support to the C front end for this.  To provide the same
checking for ordinary arrays I plan to extend the -Warray-parameter
option to the C++ front end as well as a follow up enhancement.

Besides bootstrapping/regtesting GCC I built a bunch of packages
to validate the changes.  Some "interesting" results are below.

Martin

Binutils (one instance of -Warray-bounds):
   https://sourceware.org/bugzilla/show_bug.cgi?id=26246

Glibc: one unique instance of -Warray-parameter due to a mismatch.
I considered relaxing the warning to accommodate the T[1] vs T*
difference since it's innocuous in this case.  I decided against
it because it would prevent expressing the pervasive requirement
that a function expects a pointer to an array of exactly one object.
The warning is internal to Glibc and can be easily fixed by making
the declarations consistent.

../sysdeps/nptl/pthread.h:734:47: warning: argument 1 of type ‘struct 
__jmp_buf_tag*’ declared as a pointer  [-Warray-parameter]
../setjmp/setjmp.h:54:46: note: previously declared as an array ‘struct 
__jmp_buf_tag[1]’
    54 | extern int __sigsetjmp (struct __jmp_buf_tag __env[1], int 
__savemask) __THROWNL;

Kernel (3 instances of -Warray-parameter).  This is an inconsitent
use of the idiom to use the array notation to express the requirement
that a function accesses the given number of elements.

lib/crypto/poly1305-donna64.c:15:67: warning: argument 2 of type ‘const 
u8 {aka const unsigned char}[16]’ with mismatched size [-Warray-parameter]
    15 | void poly1305_core_setkey(struct poly1305_core_key *key, const 
u8 raw_key[16])
./include/crypto/internal/poly1305.h:21:68: note: previously declared as 
‘const u8 {aka const unsigned char}*’
    21 | void poly1305_core_setkey(struct poly1305_core_key *key, const 
u8 *raw_key);

arch/x86/lib/msr-smp.c:256:51: warning: argument 2 of type ‘u32 {aka 
unsigned int}*’ declared as a pointer [-Warray-parameter]
   256./arch/x86/include/asm/msr.h:349:50: note: previously declared as 
an array ‘u32 {aka unsigned int}[8]’
   349 | int rdmsr_safe_regs_on_cpu(unsigned int cpu, u32 regs[8]);
  | int rdmsr_safe_regs_on_cpu(unsigned int cpu, u32 *regs)


[*] More details are in n2074 that WG14 discussed in 2016:
     http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2074.htm
The sentiment at the time was that proposals like N2074 would
be considered for C2X.  I still expect to submit an updated
formal proposal into an upcoming WG14 mailing.

^ permalink raw reply	[flat|nested] 33+ messages in thread

* [PATCH 1/5] infrastructure to detect out-of-bounds accesses to array parameters
  2020-07-29  1:13 [PATCH 0/5] add checking of function array parameters (PR 50584) Martin Sebor
@ 2020-07-29  1:16 ` Martin Sebor
  2020-08-07 17:08   ` Martin Sebor
  2020-07-29  1:19 ` [PATCH 2/5] C front end support " Martin Sebor
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 33+ messages in thread
From: Martin Sebor @ 2020-07-29  1:16 UTC (permalink / raw)
  To: gcc-patches

[-- Attachment #1: Type: text/plain, Size: 446 bytes --]

Patch 1 adds the basic infrastructure to support array/VLA bounds
in attribute access.  It extends the access string specification
to describe function parameters of array types (including VLAs),
extends the attr_access class to parse the string and store
the data in a form that's easy to work with, and implements
checking of various kinds f mismatches between redeclarations.
It doesn't actually enable anything new so no new tests are added.

[-- Attachment #2: gcc-50584-1.diff --]
[-- Type: text/x-patch, Size: 58250 bytes --]

[1/5] - Infrastructure to detect out-of-bounds accesses to array parameters.

gcc/ChangeLog:

	PR c/50584
	* attribs.c (decl_attributes): Also pass decl along with type
	attributes to handlers.
	(init_attr_rdwr_indices): Change second argument to attribute chain.
	Handle internal attribute representation in addition to external.
	(get_parm_access): New function.
	(attr_access::to_internal_string): Define new member function.
	(attr_access::to_external_string): Define new member function.
	(attr_access::vla_bounds): Define new member function.
	* attribs.h (struct attr_access): Declare new members.
	(attr_access::from_mode_char): Define new member function.
	(get_parm_access): Declare new function.
	* calls.c (initialize_argument_information): Pass function type
	attributes to init_attr_rdwr_indices.
	* tree-ssa-uninit.c (maybe_warn_pass_by_reference): Same.

gcc/c-family/ChangeLog:

	PR c/50584
	* c-attribs.c (c_common_attribute_table): Add "arg spec" attribute.
	(handle_argspec_attribute): New function.
	(get_argument, get_argument_type): New functions.
	(append_access_attrs): Add overload.  Handle internal attribute
	representation in addition to external.
	(handle_access_attribute): Handle internal attribute representation
	in addition to external.
	(build_attr_access_from_parms): New function.
	* c-warn.c (parm_array_as_string): Define new function.
	(plus_one):  Define new function.
	(warn_parm_array_mismatch): Define new function.

gcc/testsuite/ChangeLog:

	PR c/50584
	* gcc.dg/attr-access-read-write-2.c: Adjust text of expected messages.

diff --git a/gcc/attribs.c b/gcc/attribs.c
index 71dae123af8..3e951a4751c 100644
--- a/gcc/attribs.c
+++ b/gcc/attribs.c
@@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License
 along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
+#define INCLUDE_STRING
 #include "config.h"
 #include "system.h"
 #include "coretypes.h"
@@ -25,6 +26,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "stringpool.h"
 #include "diagnostic-core.h"
 #include "attribs.h"
+#include "fold-const.h"
 #include "stor-layout.h"
 #include "langhooks.h"
 #include "plugin.h"
@@ -32,6 +34,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "hash-set.h"
 #include "diagnostic.h"
 #include "pretty-print.h"
+#include "tree-pretty-print.h"
 #include "intl.h"
 
 /* Table of the tables of attributes (common, language, format, machine)
@@ -707,10 +710,16 @@ decl_attributes (tree *node, tree attributes, int flags,
 	  int cxx11_flag = (cxx11_attr_p ? ATTR_FLAG_CXX11 : 0);
 
 	  /* Pass in an array of the current declaration followed
-	     by the last pushed/merged declaration if  one exists.
+	     by the last pushed/merged declaration if one exists.
+	     For calls that modify the type attributes of a DECL
+	     and for which *ANODE is *NODE's type, also pass in
+	     the DECL as the third element to use in diagnostics.
 	     If the handler changes CUR_AND_LAST_DECL[0] replace
 	     *ANODE with its value.  */
-	  tree cur_and_last_decl[] = { *anode, last_decl };
+	  tree cur_and_last_decl[3] = { *anode, last_decl };
+	  if (anode != node && DECL_P (*node))
+	    cur_and_last_decl[2] = *node;
+
 	  tree ret = (spec->handler) (cur_and_last_decl, name, args,
 				      flags|cxx11_flag, &no_add_attrs);
 
@@ -2017,65 +2026,323 @@ maybe_diag_alias_attributes (tree alias, tree target)
     }
 }
 
-/* Initialize a mapping for a call to function FNDECL declared with
-   attribute access.  Each attribute positional operand inserts one
-   entry into the mapping with the operand number as the key.  */
+/* Initialize a mapping RWM for a call to a function declared with
+   attribute access in ATTRS.  Each attribute positional operand
+   inserts one entry into the mapping with the operand number as
+   the key.  */
 
 void
-init_attr_rdwr_indices (rdwr_map *rwm, tree fntype)
+init_attr_rdwr_indices (rdwr_map *rwm, tree attrs)
 {
-  if (!fntype)
+  if (!attrs)
     return;
 
-  for (tree access = TYPE_ATTRIBUTES (fntype);
+  for (tree access = attrs;
        (access = lookup_attribute ("access", access));
        access = TREE_CHAIN (access))
     {
       /* The TREE_VALUE of an attribute is a TREE_LIST whose TREE_VALUE
 	 is the attribute argument's value.  */
       tree mode = TREE_VALUE (access);
-      gcc_assert (TREE_CODE (mode) == TREE_LIST);
+      if (!mode)
+	return;
+
+      /* The (optional) list of VLA bounds.  */
+      tree vblist = TREE_CHAIN (mode);
+
       mode = TREE_VALUE (mode);
+      if (TREE_CODE (mode) != STRING_CST)
+	continue;
       gcc_assert (TREE_CODE (mode) == STRING_CST);
 
-      const char *modestr = TREE_STRING_POINTER (mode);
-      for (const char *m = modestr; *m; )
+      for (const char *m = TREE_STRING_POINTER (mode); *m; )
 	{
 	  attr_access acc = { };
 
-	  switch (*m)
+	  /* Skip the internal-only plus sign.  */
+	  if (*m == '+')
+	    ++m;
+
+	  acc.str = m;
+	  acc.mode = acc.from_mode_char (*m);
+	  acc.sizarg = UINT_MAX;
+
+	  const char *end;
+	  acc.ptrarg = strtoul (++m, const_cast<char**>(&end), 10);
+	  m = end;
+
+	  if (*m == '[')
 	    {
-	    case 'r': acc.mode = acc.read_only; break;
-	    case 'w': acc.mode = acc.write_only; break;
-	    case 'x': acc.mode = acc.read_write; break;
-	    case '-': acc.mode = acc.none; break;
-	    default: gcc_unreachable ();
+	      /* Forms containing the square bracket are internal-only
+		 (not specified by an attribute declaration), and used
+		 for various forms of array and VLA parameters.  */
+	      acc.internal_p = true;
+
+	      /* Search to the closing bracket and look at the preceding
+		 code: it determines the form of the most significant
+		 bound of the array.  Others prior to it encode the form
+		 of interior VLA bounds.  They're not of interest here.  */
+	      end = strchr (m, ']');
+	      const char *p = end;
+	      gcc_assert (p);
+
+	      while (ISDIGIT (p[-1]))
+		--p;
+
+	      if (ISDIGIT (*p))
+		{
+		  /* A digit denotes a constant bound (as in T[3]).  */
+		  acc.static_p = p[-1] == 's';
+		  acc.minsize = strtoull (p, NULL, 10);
+		}
+	      else if (' ' == p[-1])
+		{
+		  /* A space denotes an ordinary array of unspecified bound
+		     (as in T[]).  */
+		  acc.minsize = 0;
+		}
+	      else if ('*' == p[-1] || '$' == p[-1])
+		{
+		  /* An asterisk denotes a VLA.  When the closing bracket
+		     is followed by a comma and a dollar sign its bound is
+		     on the list.  Otherwise it's a VLA with an unspecified
+		     bound.  */
+		  acc.minsize = HOST_WIDE_INT_M1U;
+		}
+
+	      m = end + 1;
 	    }
 
-	  char *end;
-	  acc.ptrarg = strtoul (++m, &end, 10);
-	  m = end;
 	  if (*m == ',')
 	    {
-	      acc.sizarg = strtoul (++m, &end, 10);
-	      m = end;
+	      ++m;
+	      do
+		{
+		  if (*m == '$')
+		    {
+		      ++m;
+		      if (!acc.size)
+			{
+			  /* Extract the list of VLA bounds for the current
+			     parameter, store it in ACC.SIZE, and advance
+			     to the list of bounds for the next VLA parameter.
+			  */
+			  acc.size = TREE_VALUE (vblist);
+			  vblist = TREE_CHAIN (vblist);
+			}
+		    }
+
+		  if (ISDIGIT (*m))
+		    {
+		      /* Extract the positional argument.  It's absent
+			 for VLAs whose bound doesn't name a function
+			 parameter.  */
+		      unsigned pos = strtoul (m, const_cast<char**>(&end), 10);
+		      if (acc.sizarg == UINT_MAX)
+			acc.sizarg = pos;
+		      m = end;
+		    }
+		}
+	      while (*m == '$');
 	    }
-	  else
-	    acc.sizarg = UINT_MAX;
 
-	  acc.ptr = NULL_TREE;
-	  acc.size = NULL_TREE;
+	  acc.end = m;
+
+	  bool existing;
+	  auto &ref = rwm->get_or_insert (acc.ptrarg, &existing);
+	  if (existing)
+	    {
+	      /* Merge the new spec with the existing.  */
+	      if (acc.minsize == HOST_WIDE_INT_M1U)
+		ref.minsize = HOST_WIDE_INT_M1U;
+
+	      if (acc.sizarg != UINT_MAX)
+		ref.sizarg = acc.sizarg;
+
+	      if (acc.mode)
+		ref.mode = acc.mode;
+	    }
+	  else
+	    ref = acc;
 
 	  /* Unconditionally add an entry for the required pointer
 	     operand of the attribute, and one for the optional size
 	     operand when it's specified.  */
-	  rwm->put (acc.ptrarg, acc);
 	  if (acc.sizarg != UINT_MAX)
 	    rwm->put (acc.sizarg, acc);
 	}
     }
 }
 
+/* Return the access specification for a function parameter PARM
+   or null if the current function has no such specification.  */
+
+attr_access *
+get_parm_access (rdwr_map &rdwr_idx, tree parm,
+		 tree fndecl /* = current_function_decl */)
+{
+  tree fntype = TREE_TYPE (fndecl);
+  init_attr_rdwr_indices (&rdwr_idx, TYPE_ATTRIBUTES (fntype));
+
+  if (rdwr_idx.is_empty ())
+    return NULL;
+
+  unsigned argpos = 0;
+  tree fnargs = DECL_ARGUMENTS (fndecl);
+  for (tree arg = fnargs; arg; arg = TREE_CHAIN (arg), ++argpos)
+    if (arg == parm)
+      return rdwr_idx.get (argpos);
+
+  return NULL;
+}
+
+/* Return the internal representation as STRING_CST.  Internal positional
+   arguments are zero-based.  */
+
+tree
+attr_access::to_internal_string () const
+{
+  return build_string (end - str, str);
+}
+
+/* Return the human-readable representation of the external attribute
+   specification (as it might appear in the source code) as STRING_CST.
+   External positional arguments are one-based.  */
+
+tree
+attr_access::to_external_string () const
+{
+  char buf[80];
+  gcc_assert (mode != deferred);
+  int len = snprintf (buf, sizeof buf, "access (%s, %u",
+		      mode_names[mode], ptrarg + 1);
+  if (sizarg != UINT_MAX)
+    len += snprintf (buf + len, sizeof buf - len, ", %u", sizarg + 1);
+  strcpy (buf + len, ")");
+  return build_string (len + 2, buf);
+}
+
+/* Return the number of specified VLA bounds and set *nunspec to
+   the number of unspecified ones (those designated by [*]).  */
+
+unsigned
+attr_access::vla_bounds (unsigned *nunspec) const
+{
+  *nunspec = 0;
+  for (const char* p = strrchr (str, ']'); p && *p != '['; --p)
+    if (*p == '*')
+      ++*nunspec;
+  return list_length (size);
+}
+
+
+/* Defined in attr_access.  */
+constexpr char attr_access::mode_chars[];
+constexpr char attr_access::mode_names[][11];
+
+/* Format an array, including a VLA, pointed to by TYPE and used as
+   a function parameter as a human-readable string.  ACC describes
+   an access to the parameter and is used to determine the outermost
+   form of the array including its bound which is otherwise obviated
+   by its decay to pointer.  Return the formatted string.  */
+
+std::string
+attr_access::array_as_string (tree type) const
+{
+  std::string str;
+
+  if (type == error_mark_node)
+    return std::string ();
+
+  bool restrict_p = TYPE_RESTRICT (type);
+  if (TREE_CODE (type) == POINTER_TYPE)
+    type = TREE_TYPE (type);
+
+  if (this->str == NULL)
+    str = "*";
+  else
+    {
+      str = '[';
+      if (restrict_p)
+	str += "restrict ";
+      if (this->minsize == 0)
+	; /* Empty brackets.  */
+      else if (this->minsize == HOST_WIDE_INT_M1U)
+	{
+	  const char *p = strrchr (this->str, ']');
+	  if (p && p[-1] == '$' && this->size)
+	    {
+	      tree bound = TREE_VALUE (this->size);
+	      const char *bndstr = print_generic_expr_to_str (bound);
+	      str += bndstr;
+	    }
+	  else
+	    str += '*';
+	}
+      else
+	{
+	  char buf[22];
+	  sprintf (buf, "%llu", (unsigned long long)this->minsize);
+	  if (this->static_p)
+	    str += "static ";
+	  str += buf;
+	}
+
+      if (!str.empty () && str.end ()[-1] == ' ')
+	/* Strip any trailing space.  */
+	str.resize (str.length () - 1);
+      str += ']';
+    }
+
+  for (; TREE_CODE (type) == ARRAY_TYPE; type = TREE_TYPE (type))
+    {
+      tree dom = TYPE_DOMAIN (type);
+      if (!dom)
+	{
+	  str += "[*]";
+	  continue;
+	}
+
+      tree max = TYPE_MAX_VALUE (dom);
+      if (!max)
+	{
+	  str += "[*]";
+	  continue;
+	}
+
+      const char *maxstr;
+      if (TREE_CODE (max) == INTEGER_CST)
+	{
+	  tree t = TREE_TYPE (max);
+	  max = fold_build2 (PLUS_EXPR, t, max, build_int_cst (t, 1));
+	  maxstr = print_generic_expr_to_str (max);
+	}
+      else
+	{
+	  if (TREE_CODE (max) == NOP_EXPR)
+	    max = TREE_OPERAND (max, 0);
+	  if (TREE_CODE (max) == PLUS_EXPR
+	      && TREE_CODE (TREE_OPERAND (max, 1)) == INTEGER_CST
+	      && integer_all_onesp (TREE_OPERAND (max, 1)))
+	    max = TREE_OPERAND (max, 0);
+	  if (TREE_CODE (max) == NOP_EXPR)
+	    max = TREE_OPERAND (max, 0);
+	  if (TREE_CODE (max) == SAVE_EXPR)
+	    max = TREE_OPERAND (max, 0);
+	  maxstr = print_generic_expr_to_str (max);
+	}
+
+      str += '[';
+      str += maxstr;
+      str += ']';
+    }
+
+  pretty_printer pp;
+  dump_generic_node (&pp, type, 0, TDF_VOPS|TDF_MEMSYMS, false);
+  const char *eltypestr = pp_formatted_text (&pp);
+  str.insert (str.begin (), eltypestr, eltypestr + strlen (eltypestr));
+  return str;
+}
 
 #if CHECKING_P
 
diff --git a/gcc/attribs.h b/gcc/attribs.h
index dea0b6c44e6..d86672795fe 100644
--- a/gcc/attribs.h
+++ b/gcc/attribs.h
@@ -224,20 +224,91 @@ lookup_attribute_by_prefix (const char *attr_name, tree list)
 
 struct attr_access
 {
+  /* The beginning and end of the internal string representation.  */
+  const char *str, *end;
   /* The attribute pointer argument.  */
   tree ptr;
-  /* The size of the pointed-to object or NULL when not specified.  */
+  /* For a declaration, a TREE_CHAIN of VLA bound expressions stored
+     in TREE_VALUE and their positions in the argument list (stored
+     in TREE_PURPOSE).  Each expression may be a PARM_DECL or some
+     other DECL (for ordinary variables), or an EXPR for other
+     expressions (e.g., funcion calls).  */
   tree size;
 
-  /* The zero-based number of each of the formal function arguments.  */
+  /* The zero-based position of each of the formal function arguments.
+     For the optional SIZARG, UINT_MAX when not specified.  For VLAs
+     with multiple variable bounds, SIZARG is the position corresponding
+     to the most significant bound in the argument list.  Positions of
+     subsequent bounds are in the TREE_PURPOSE field of the SIZE chain.  */
   unsigned ptrarg;
   unsigned sizarg;
+  /* For internal specifications only, the constant minimum size of
+     the array, zero if not specified, and HWI_M1U for the unspecified
+     VLA [*] notation.  Meaningless for external (explicit) access
+     specifications.  */
+  unsigned HOST_WIDE_INT minsize;
 
   /* The access mode.  */
-  enum access_mode { none, read_only, write_only, read_write };
+  enum access_mode
+    {
+     none = 0,
+     read_only = 1,
+     write_only = 2,
+     read_write = read_only | write_only,
+     /* In an internal representation defers to the presence of
+	the const qualifier (treated as likely read_only) or to
+	an external/explicit specification of the attribute.  */
+     deferred
+    };
   access_mode mode;
+
+  /* Set for an attribute added internally rather than by an explicit
+     declaration. */
+  bool internal_p;
+  /* Set for the T[static MINSIZE] array notation for nonzero MINSIZE
+     less than HWI_M1U.  */
+  bool static_p;
+
+  /* Return the number of specified VLA bounds.  */
+  unsigned vla_bounds (unsigned *) const;
+
+  /* Return internal representation as STRING_CST.  */
+  tree to_internal_string () const;
+
+  /* Return the human-readable representation of the external attribute
+     specification (as it might appear in the source code) as STRING_CST.  */
+  tree to_external_string () const;
+
+  /* Return argument of array type formatted as a readable string.  */
+  std::string array_as_string (tree) const;
+
+  /* Return the access mode corresponding to the character code.  */
+  static access_mode from_mode_char (char);
+
+  /* The character codes corresponding to all the access modes.  */
+  static constexpr char mode_chars[5] = { '-', 'r', 'w', 'x', '^' };
+
+  /* The strings corresponding to just the external access modes.  */
+  static constexpr char mode_names[4][11] =
+    {
+     "none", "read_only", "write_only", "read_write"
+    };
 };
 
+inline attr_access::access_mode
+attr_access::from_mode_char (char c)
+{
+  switch (c)
+    {
+    case mode_chars[none]: return none;
+    case mode_chars[read_only]: return read_only;
+    case mode_chars[write_only]: return write_only;
+    case mode_chars[read_write]: return read_write;
+    case mode_chars[deferred]: return deferred;
+    }
+  gcc_unreachable ();
+}
+
 /* Used to define rdwr_map below.  */
 struct rdwr_access_hash: int_hash<int, -1> { };
 
@@ -247,5 +318,7 @@ struct attr_access;
 typedef hash_map<rdwr_access_hash, attr_access> rdwr_map;
 
 extern void init_attr_rdwr_indices (rdwr_map *, tree);
+extern attr_access *get_parm_access (rdwr_map &, tree,
+				     tree = current_function_decl);
 
 #endif // GCC_ATTRIBS_H
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index 37214831538..452600cbfc6 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License
 along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
+#define INCLUDE_STRING
 #include "config.h"
 #include "system.h"
 #include "coretypes.h"
@@ -45,6 +46,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "opts.h"
 #include "gimplify.h"
 #include "tree-pretty-print.h"
+#include "gcc-rich-location.h"
 
 static tree handle_packed_attribute (tree *, tree, tree, int, bool *);
 static tree handle_nocommon_attribute (tree *, tree, tree, int, bool *);
@@ -136,6 +138,7 @@ static tree handle_target_clones_attribute (tree *, tree, tree, int, bool *);
 static tree handle_optimize_attribute (tree *, tree, tree, int, bool *);
 static tree ignore_attribute (tree *, tree, tree, int, bool *);
 static tree handle_no_split_stack_attribute (tree *, tree, tree, int, bool *);
+static tree handle_argspec_attribute (tree *, tree, tree, int, bool *);
 static tree handle_fnspec_attribute (tree *, tree, tree, int, bool *);
 static tree handle_warn_unused_attribute (tree *, tree, tree, int, bool *);
 static tree handle_returns_nonnull_attribute (tree *, tree, tree, int, bool *);
@@ -434,6 +437,10 @@ const struct attribute_spec c_common_attribute_table[] =
 			      ignore_attribute, NULL },
   { "no_split_stack",	      0, 0, true,  false, false, false,
 			      handle_no_split_stack_attribute, NULL },
+  /* For internal use only (marking of function arguments).
+     The name contains a space to prevent its usage in source code.  */
+  { "arg spec",		      1, -1, true, false, false, false,
+			      handle_argspec_attribute, NULL },
   /* For internal use (marking of builtins and runtime functions) only.
      The name contains space to prevent its usage in source code.  */
   { "fn spec",		      1, 1, false, true, true, false,
@@ -3035,8 +3042,22 @@ handle_assume_aligned_attribute (tree *node, tree name, tree args, int,
   return NULL_TREE;
 }
 
-/* Handle a "fn spec" attribute; arguments as in
-   struct attribute_spec.handler.  */
+/* Handle the internal-only "arg spec" attribute.  */
+
+static tree
+handle_argspec_attribute (tree *, tree, tree args, int, bool *)
+{
+  /* Verify the attribute has one or two arguments and their kind.  */
+  gcc_assert (args && TREE_CODE (TREE_VALUE (args)) == STRING_CST);
+  for (tree next = TREE_CHAIN (args); next; next = TREE_CHAIN (next))
+    {
+      tree val = TREE_VALUE (next);
+      gcc_assert (DECL_P (val) || EXPR_P (val));
+    }
+  return NULL_TREE;
+}
+
+/* Handle the internal-only "fn spec" attribute.  */
 
 static tree
 handle_fnspec_attribute (tree *node ATTRIBUTE_UNUSED, tree ARG_UNUSED (name),
@@ -3822,7 +3843,8 @@ get_argument_type (tree functype, unsigned argno, unsigned *nargs)
 	  tree argtype = function_args_iter_cond (&iter);
 	  if (VOID_TYPE_P (argtype))
 	    break;
-	  return argtype;
+	  if (argtype != error_mark_node)
+	    return argtype;
 	}
     }
 
@@ -3830,143 +3852,271 @@ get_argument_type (tree functype, unsigned argno, unsigned *nargs)
   return NULL_TREE;
 }
 
-/* Appends ATTRSTR to the access string in ATTRS if one is there
-   or creates a new one and returns the concatenated access string.  */
+/* Given a function FNDECL return the function argument at the zero-
+   based position ARGNO or null if it can't be found.  */
 
 static tree
-append_access_attrs (tree t, tree attrs, const char *attrstr,
-		     char code, HOST_WIDE_INT idxs[2])
+get_argument (tree fndecl, unsigned argno)
 {
-  char attrspec[80];
-  int n1 = sprintf (attrspec, "%c%u", code, (unsigned) idxs[0] - 1);
-  int n2 = 0;
-  if (idxs[1])
-    n2 = sprintf (attrspec + n1 + 1, "%u", (unsigned) idxs[1] - 1);
+  if (!DECL_P (fndecl))
+    return NULL_TREE;
 
-  size_t newlen = n1 + n2 + !!n2;
-  char *newspec = attrspec;
+  unsigned i = 0;
+  for (tree arg = DECL_ARGUMENTS (fndecl); arg; arg = TREE_CHAIN (arg))
+    if (i++ == argno)
+      return arg;
 
-  if (tree acs = lookup_attribute ("access", attrs))
-    {
-      /* The TREE_VALUE of an attribute is a TREE_LIST whose TREE_VALUE
-	 is the attribute argument's value.  */
-      acs = TREE_VALUE (acs);
-      gcc_assert (TREE_CODE (acs) == TREE_LIST);
-      acs = TREE_VALUE (acs);
-      gcc_assert (TREE_CODE (acs) == STRING_CST);
+  return NULL_TREE;
+}
 
-      /* Check to make sure ATTRSPEC doesn't conflict with another
-	 access attribute specified in ATTRS by searching the access
-	 string in ATTRS for the position string formatted above into
-	 ATTRSPEC, and if it's found, that the two match.  */
+/* Attempt to append attribute access specification ATTRSPEC, optionally
+   described by the human-readable string ATTRSTR, for type T, to one in
+   ATTRS. VBLIST is an optional list of bounds of variable length array
+   parameters described by ATTRSTR.
+   Issue warning for conflicts and return null if any are found.
+   Return the concatenated access string on success.  */
 
-      const char *posstr = attrspec + 1;
-      const char *str = TREE_STRING_POINTER (acs);
-      const char *pos = str;
-      for ( ; ; pos += n1)
+static tree
+append_access_attr (tree node[3], tree attrs, const char *attrstr,
+		    const char *attrspec, tree vblist = NULL_TREE)
+{
+  tree argstr = build_string (strlen (attrspec) + 1, attrspec);
+  tree ataccess = tree_cons (NULL_TREE, argstr, vblist);
+  ataccess = tree_cons (get_identifier ("access"), ataccess, NULL_TREE);
+
+  /* The access specification being applied.  This may be an implicit
+     access spec synthesized for array (or VLA) parameters even for
+     a declaration with an explicit access spec already applied, if
+     this call corresponds to the first declaration of the function.  */
+  rdwr_map new_idxs;
+  init_attr_rdwr_indices (&new_idxs, ataccess);
+
+  /* The current access specification alrady applied.  */
+  rdwr_map cur_idxs;
+  init_attr_rdwr_indices (&cur_idxs, attrs);
+
+  std::string spec;
+  for (auto it = new_idxs.begin (); it != new_idxs.end (); ++it)
+    {
+      const auto &newaxsref = *it;
+
+      /* The map has two equal entries for each pointer argument that
+	 has an associated size argument.  Process just the entry for
+	 the former.  */
+      if ((unsigned)newaxsref.first != newaxsref.second.ptrarg)
+	continue;
+
+      const attr_access* const cura = cur_idxs.get (newaxsref.first);
+      if (!cura)
 	{
-	  pos = strstr (pos, posstr);
-	  if (!pos)
-	    break;
+	  /* The new attribute needs to be added.  */
+	  tree str = newaxsref.second.to_internal_string ();
+	  spec += TREE_STRING_POINTER (str);
+	  continue;
+	}
+
+      /* The new access spec refers to an array/pointer argument for
+	 which an access spec already exists.  Check and diagnose any
+	 conflicts.  If no conflicts are found, merge the two.  */
+      const attr_access* const newa = &newaxsref.second;
+
+      if (!attrstr)
+	{
+	  tree str = NULL_TREE;
+	  if (newa->mode != attr_access::deferred)
+	    str = newa->to_external_string ();
+	  else if (cura->mode != attr_access::deferred)
+	    str = cura->to_external_string ();
+	  if (str)
+	    attrstr = TREE_STRING_POINTER (str);
+	}
+
+      location_t curloc = input_location;
+      if (node[2] && DECL_P (node[2]))
+	curloc = DECL_SOURCE_LOCATION (node[2]);
+
+      location_t prevloc = UNKNOWN_LOCATION;
+      if (node[1] && DECL_P (node[1]))
+	prevloc = DECL_SOURCE_LOCATION (node[1]);
+
+      if (newa->mode != cura->mode
+	  && newa->mode != attr_access::deferred
+	  && cura->mode != attr_access::deferred
+	  && newa->internal_p == cura->internal_p)
+	{
+	  /* Mismatch in access mode.  */
+	  auto_diagnostic_group d;
+	  if (warning_at (curloc, OPT_Wattributes,
+			  "attribute %qs mismatch with mode %qs",
+			  attrstr, cura->mode_names[cura->mode])
+	      && prevloc != UNKNOWN_LOCATION)
+	    inform (prevloc, "previous declaration here");
+	  continue;
+	}
+
+      /* Set if PTRARG refers to a VLA with an unspecified bound (T[*]).
+	 Be prepared for either CURA or NEWA to refer to it, depending
+	 on which happens to come first in the declaration.  */
+      const bool cur_vla_ub = (cura->internal_p
+			       && cura->sizarg == UINT_MAX
+			       && cura->minsize == HOST_WIDE_INT_M1U);
+      const bool new_vla_ub = (newa->internal_p
+			       && newa->sizarg == UINT_MAX
+			       && newa->minsize == HOST_WIDE_INT_M1U);
+
+      if (newa->sizarg != cura->sizarg
+	  && attrstr
+	  && (!(cur_vla_ub ^ new_vla_ub)
+	      || (!cura->internal_p && !newa->internal_p)))
+	{
+	  /* Avoid diagnosing redeclarations of functions with no explicit
+	     attribute access that add one.  */
+	  if (newa->mode == attr_access::deferred
+	      && cura->mode != attr_access::deferred
+	      && newa->sizarg == UINT_MAX
+	      && cura->sizarg != UINT_MAX)
+	    continue;
 
-	  if (ISDIGIT (pos[-1]) || ISDIGIT (pos[n1 -1]))
+	  if (cura->mode == attr_access::deferred
+	      && newa->mode != attr_access::deferred
+	      && cura->sizarg == UINT_MAX
+	      && newa->sizarg != UINT_MAX)
 	    continue;
 
-	  /* Found a matching positional argument.  */
-	  if (*attrspec != pos[-1])
+	  /* The two specs designate different size arguments.  It's okay
+	     for the explicit spec to specify a size where none is provided
+	     by the implicit (VLA) one, as in:
+	       __attribute__ ((access (read_write, 1, 2)))
+	       void f (int*, int);
+	     but not for two explicit access attributes to do that.  */
+	  bool warned = false;
+
+	  auto_diagnostic_group d;
+
+	  if (newa->sizarg == UINT_MAX)
+	    /* Mismatch in the presence of the size argument.  */
+	    warned = warning_at (curloc, OPT_Wattributes,
+				 "attribute %qs missing positional argument 2 "
+				 "provided in previous designation by argument "
+				 "%u", attrstr, cura->sizarg + 1);
+	  else if (cura->sizarg == UINT_MAX)
+	    /* Mismatch in the presence of the size argument.  */
+	    warned = warning_at (curloc, OPT_Wattributes,
+				 "attribute %qs positional argument 2 "
+				 "missing in previous designation",
+				 attrstr);
+	  else if (newa->internal_p || cura->internal_p)
 	    {
-	      const char* const modestr
-		= (pos[-1] == 'r'
-		   ? "read_only"
-		   : (pos[-1] == 'w'
-		      ? "write_only"
-		      : (pos[-1] == 'x' ? "read_write" : "none")));
-	      /* Mismatch in access mode.  */
-	      auto_diagnostic_group d;
-	      if (warning (OPT_Wattributes,
-			   "attribute %qs mismatch with mode %qs",
-			   attrstr, modestr)
-		  && DECL_P (t))
-		inform (DECL_SOURCE_LOCATION (t),
-			"previous declaration here");
-	      return NULL_TREE;
+	      /* Mismatch in the value of the size argument and a VLA
+		 bound.  */
+	      location_t argloc = curloc;
+	      if (tree arg = get_argument (node[2], newa->sizarg))
+		argloc = DECL_SOURCE_LOCATION (arg);
+	      warned = warning_at (argloc, OPT_Wattributes,
+				   "attribute %qs positional argument 2 "
+				   "conflicts with previous designation "
+				   "by argument %u",
+				   attrstr, cura->sizarg + 1);
 	    }
-
-	  if ((n2 && pos[n1 - 1] != ','))
+	  else
+	    /* Mismatch in the value of the size argument between two
+	       explicit access attributes.  */
+	    warned = warning_at (curloc, OPT_Wattributes,
+				 "attribute %qs mismatched positional argument "
+				 "values %i and %i",
+				 attrstr, newa->sizarg + 1, cura->sizarg + 1);
+
+	  if (warned)
 	    {
-	      /* Mismatch in the presence of the size argument.  */
-	      auto_diagnostic_group d;
-	      if (warning (OPT_Wattributes,
-			   "attribute %qs positional argument 2 conflicts "
-			   "with previous designation",
-			   attrstr)
-		  && DECL_P (t))
-		inform (DECL_SOURCE_LOCATION (t),
-			"previous declaration here");
-	      return NULL_TREE;
-	    }
+	      /* If the previous declaration is a function (as opposed
+		 to a typedef of one), find the location of the array
+		 or pointer argument that uses the conflicting VLA bound
+		 and point to it in the note.  */
+	      const attr_access* const pa = cura->size ? cura : newa;
+	      tree size = pa->size ? TREE_VALUE (pa->size) : NULL_TREE;
+	      if (size && DECL_P (size))
+		{
+		  location_t argloc = UNKNOWN_LOCATION;
+		  if (tree arg = get_argument (node[2], pa->ptrarg))
+		    argloc = DECL_SOURCE_LOCATION (arg);
 
-	  if (!n2 && pos[n1 - 1] == ',')
-	    {
-	      /* Mismatch in the presence of the size argument.  */
-	      auto_diagnostic_group d;
-	      if (warning (OPT_Wattributes,
-			   "attribute %qs missing positional argument 2 "
-			   "provided in previous designation",
-			   attrstr)
-		  && DECL_P (t))
-		inform (DECL_SOURCE_LOCATION (t),
-			"previous declaration here");
-	      return NULL_TREE;
-	    }
+		  gcc_rich_location richloc (DECL_SOURCE_LOCATION (size));
+		  if (argloc != UNKNOWN_LOCATION)
+		    richloc.add_range (argloc);
 
-	  if (n2 && strncmp (attrspec + n1 + 1, pos + n1, n2))
-	    {
-	      /* Mismatch in the value of the size argument.  */
-	      auto_diagnostic_group d;
-	      if (warning (OPT_Wattributes,
-			   "attribute %qs mismatched positional argument "
-			   "values %i and %i",
-			   attrstr, atoi (attrspec + n1 + 1) + 1,
-			   atoi (pos + n1) + 1)
-		  && DECL_P (t))
-		inform (DECL_SOURCE_LOCATION (t),
-			"previous declaration here");
-	      return NULL_TREE;
+		  inform (&richloc, "designating the bound of variable "
+			  "length array argument %u",
+			  pa->ptrarg + 1);
+		}
+	      else if (prevloc != UNKNOWN_LOCATION)
+		inform (prevloc, "previous declaration here");
 	    }
 
-	  /* Avoid adding the same attribute specification.  */
-	  return NULL_TREE;
+	  continue;
 	}
 
-      /* Connect the two substrings formatted above into a single one.  */
-      if (idxs[1])
-	attrspec[n1] = ',';
+      if (newa->internal_p == cura->internal_p)
+	continue;
 
-      size_t len = strlen (str);
-      newspec = XNEWVEC (char, newlen + len + 1);
-      strcpy (newspec, str);
-      strcpy (newspec + len, attrspec);
-      newlen += len;
+      /* Merge the CURA and NEWA.  */
+      attr_access merged = newaxsref.second;
+
+      /* VLA seen in a declaration takes precedence.  */
+      if (cura->minsize == HOST_WIDE_INT_M1U)
+	merged.minsize = HOST_WIDE_INT_M1U;
+
+      /* Use the explicitly specified size positional argument.  */
+      if (cura->sizarg != UINT_MAX)
+	merged.sizarg = cura->sizarg;
+
+      /* Use the explicitly specified mode.  */
+      if (merged.mode == attr_access::deferred)
+	merged.mode = cura->mode;
+
+      tree str = merged.to_internal_string ();
+      spec += TREE_STRING_POINTER (str);
     }
-  else if (idxs[1])
-    /* Connect the two substrings formatted above into a single one.  */
-    attrspec[n1] = ',';
 
-  tree ret = build_string (newlen + 1, newspec);
-  if (newspec != attrspec)
-    XDELETEVEC (newspec);
-  return ret;
+  if (!spec.length ())
+    return NULL_TREE;
+
+  return build_string (spec.length (), spec.c_str ());
 }
 
-/* Handle the access attribute (read_only, write_only, and read_write).  */
+/* Convenience wrapper for the above.  */
+
+tree
+append_access_attr (tree node[3], tree attrs, const char *attrstr,
+		    char code, HOST_WIDE_INT idxs[2])
+{
+  char attrspec[80];
+  int n = sprintf (attrspec, "%c%u", code, (unsigned) idxs[0] - 1);
+  if (idxs[1])
+    n += sprintf (attrspec + n, ",%u", (unsigned) idxs[1] - 1);
+
+  return append_access_attr (node, attrs, attrstr, attrspec);
+}
+
+/* Handle the access attribute for function type NODE[0], with the function
+   DECL optionally in NODE[1].  The handler is called both in response to
+   an explict attribute access on a declaration with a mode and one or two
+   positional arguments, and for internally synthesized access specifications
+   with a string argument optionally followd by a DECL or expression
+   representing a VLA bound.  To speed up parsing, the handler transforms
+   the attribute and its arguments into a string.  */
 
 static tree
-handle_access_attribute (tree *node, tree name, tree args,
+handle_access_attribute (tree node[3], tree name, tree args,
 			 int ARG_UNUSED (flags), bool *no_add_attrs)
 {
+  tree attrs = TYPE_ATTRIBUTES (*node);
   tree type = *node;
-  tree attrs = TYPE_ATTRIBUTES (type);
+  if (POINTER_TYPE_P (type))
+    {
+      tree ptype = TREE_TYPE (type);
+      if (FUNC_OR_METHOD_TYPE_P (ptype))
+	type = ptype;
+    }
 
   *no_add_attrs = true;
 
@@ -3984,9 +4134,32 @@ handle_access_attribute (tree *node, tree name, tree args,
   tree access_mode = TREE_VALUE (args);
   if (TREE_CODE (access_mode) == STRING_CST)
     {
-      /* This must be a recursive call to handle the condensed internal
-	 form of the attribute (see below).  Since all validation has
-	 been done simply return here, accepting the attribute as is.  */
+      const char* const str = TREE_STRING_POINTER (access_mode);
+      if (*str == '+')
+	{
+	  /* This is a request to merge an internal specification for
+	     a function declaration involving arrays but no explicit
+	     attribute access.  */
+	  tree vblist = TREE_CHAIN (args);
+	  tree axstr = append_access_attr (node, attrs, NULL, str + 1,
+					   vblist);
+	  if (!axstr)
+	    return NULL_TREE;
+
+	  /* Replace any existing access attribute specification with
+	     the concatenation above.  */
+	  tree axsat = tree_cons (NULL_TREE, axstr, vblist);
+	  axsat = tree_cons (name, axsat, NULL_TREE);
+
+	  /* Recursively call self to "replace" the documented/external
+	     form of the attribute with the condensend internal form.  */
+	  decl_attributes (node, axsat, flags);
+	  return NULL_TREE;
+	}
+
+      /* This is a recursive call to handle the condensed internal form
+	 of the attribute (see below).  Since all validation has been
+	 done simply return here, accepting the attribute as is.  */
       *no_add_attrs = false;
       return NULL_TREE;
     }
@@ -4017,16 +4190,28 @@ handle_access_attribute (tree *node, tree name, tree args,
 	ps += 2;
     }
 
-  const bool read_only = !strncmp (ps, "read_only", 9);
-  const bool write_only = !strncmp (ps, "write_only", 10);
-  const bool read_write = !strncmp (ps, "read_write", 10);
-  if (!read_only && !write_only && !read_write && strncmp (ps, "none", 4))
-    {
-      error ("attribute %qE invalid mode %qs; expected one of "
-	     "%qs, %qs, %qs, or %qs", name, access_str,
-	     "read_only", "read_write", "write_only", "none");
-      return NULL_TREE;
-    }
+  int imode;
+
+  {
+    const int nmodes =
+      sizeof attr_access::mode_names / sizeof *attr_access::mode_names;
+
+    for (imode = 0; imode != nmodes; ++imode)
+      if (!strncmp (ps, attr_access::mode_names[imode],
+		    strlen (attr_access::mode_names[imode])))
+	break;
+
+    if (imode == nmodes)
+      {
+	error ("attribute %qE invalid mode %qs; expected one of "
+	       "%qs, %qs, %qs, or %qs", name, access_str,
+	       "read_only", "read_write", "write_only", "none");
+	return NULL_TREE;
+      }
+  }
+
+  const attr_access::access_mode mode =
+    static_cast<attr_access::access_mode>(imode);
 
   if (funcall)
     {
@@ -4149,7 +4334,7 @@ handle_access_attribute (tree *node, tree name, tree args,
       }
   }
 
-  if (read_write || write_only)
+  if (mode == attr_access::read_write || mode == attr_access::write_only)
     {
       /* Read_write and write_only modes must reference non-const
 	 arguments.  */
@@ -4182,35 +4367,164 @@ handle_access_attribute (tree *node, tree name, tree args,
   /* Verify that the new attribute doesn't conflict with any existing
      attributes specified on previous declarations of the same type
      and if not, concatenate the two.  */
-  const char code
-    = read_only ? 'r' : write_only ? 'w' : read_write ? 'x' : '-';
-  tree new_attrs = append_access_attrs (node[0], attrs, attrstr, code, idxs);
+  const char code = attr_access::mode_chars[mode];
+  tree new_attrs = append_access_attr (node, attrs, attrstr, code, idxs);
   if (!new_attrs)
     return NULL_TREE;
 
   /* Replace any existing access attribute specification with
      the concatenation above.  */
   new_attrs = tree_cons (NULL_TREE, new_attrs, NULL_TREE);
-  new_attrs = tree_cons (name, new_attrs, attrs);
+  new_attrs = tree_cons (name, new_attrs, NULL_TREE);
 
   if (node[1])
     {
       /* Repeat for the previously declared type.  */
       attrs = TYPE_ATTRIBUTES (TREE_TYPE (node[1]));
-      tree attrs1 = append_access_attrs (node[1], attrs, attrstr, code, idxs);
-      if (!attrs1)
+      new_attrs = append_access_attr (node, attrs, attrstr, code, idxs);
+      if (!new_attrs)
 	return NULL_TREE;
 
-      attrs1 = tree_cons (NULL_TREE, attrs1, NULL_TREE);
-      new_attrs = tree_cons (name, attrs1, attrs);
+      new_attrs = tree_cons (NULL_TREE, new_attrs, NULL_TREE);
+      new_attrs = tree_cons (name, new_attrs, NULL_TREE);
     }
 
   /* Recursively call self to "replace" the documented/external form
-     of the attribute with the condensend internal form.  */
+     of the attribute with the condensed internal form.  */
   decl_attributes (node, new_attrs, flags);
   return NULL_TREE;
 }
 
+/* Extract attribute "arg spec" from each FNDECL argument that has it,
+   build a single attribute access corresponding to all the arguments,
+   and return the result.  SKIP_VOIDPTR set to ignore void* parameters
+   (used for user-defined functions for which, unlike in for built-ins,
+   void* cannot be relied on to determine anything about the access
+   through it or whether it even takes place).
+
+   For example, the parameters in the declaration:
+
+     void f (int x, int y, char [x][1][y][3], char [y][2][y][5]);
+
+   result in the following attribute access:
+
+     value: "+^2[*],$0$1^3[*],$1$1"
+     chain: <0, x> <1, y>
+
+   where each <node> on the chain corresponds to one VLA bound for each
+   of the two parameters.  */
+
+tree
+build_attr_access_from_parms (tree parms, bool skip_voidptr)
+{
+  /* Maps each named integral argument DECL seen so far to its position
+     in the argument list; used to associate VLA sizes with arguments.  */
+  hash_map<tree, unsigned> arg2pos;
+
+  /* The string representation of the access specification for all
+     arguments.  */
+  std::string spec;
+  unsigned argpos = 0;
+
+  /* A TREE_LIST of VLA bounds.  */
+  tree vblist = NULL_TREE;
+
+  for (tree arg = parms; arg; arg = TREE_CHAIN (arg), ++argpos)
+    {
+      if (!DECL_P (arg))
+	continue;
+
+      tree argtype = TREE_TYPE (arg);
+      if (DECL_NAME (arg) && INTEGRAL_TYPE_P (argtype))
+	arg2pos.put (arg, argpos);
+
+      tree argspec = DECL_ATTRIBUTES (arg);
+      if (!argspec)
+	continue;
+
+      if (POINTER_TYPE_P (argtype))
+	{
+	  /* void* arguments in user-defined functions could point to
+	     anything; skip them.  */
+	  tree reftype = TREE_TYPE (argtype);
+	  if (skip_voidptr && VOID_TYPE_P (reftype))
+	    continue;
+	}
+
+      /* Each parameter should have at most one "arg spec" attribute.  */
+      argspec = lookup_attribute ("arg spec", argspec);
+      if (!argspec)
+	continue;
+
+      /* Attribute arg spec should have one or two arguments.  */
+      argspec = TREE_VALUE (argspec);
+
+      /* The attribute arg spec string.  */
+      tree str = TREE_VALUE (argspec);
+      const char *s = TREE_STRING_POINTER (str);
+
+      /* Create the attribute access string from the arg spec string,
+	 optionally followed by position of the VLA bound argument if
+	 it is one.  */
+      char specbuf[80];
+      int len = snprintf (specbuf, sizeof specbuf, "%c%u%s",
+			  attr_access::mode_chars[attr_access::deferred],
+			  argpos, s);
+      gcc_assert ((size_t) len < sizeof specbuf);
+
+      if (!spec.length ())
+	spec += '+';
+
+      spec += specbuf;
+
+      /* The (optional) list of expressions denoting the VLA bounds
+	 N in ARGTYPE <arg>[Ni]...[Nj]...[Nk].  */
+      tree argvbs = TREE_CHAIN (argspec);
+      if (argvbs)
+	{
+	  spec += ',';
+	  /* Add ARGVBS to the list.  Their presence is indicated by
+	     appending a comma followed by the dollar sign and, when
+	     it corresponds to a function parameter, the position of
+	     each bound Ni, so it can be distinguished from
+	     an unspecified bound (as in T[*]).  The list is in reverse
+	     order of arguments and needs to be reversed to access in
+	     order.  */
+	  vblist = tree_cons (NULL_TREE, argvbs, vblist);
+
+	  unsigned nelts = 0;
+	  for (tree vb = argvbs; vb; vb = TREE_CHAIN (vb), ++nelts)
+	    {
+	      tree bound = TREE_VALUE (vb);
+	      if (const unsigned *psizpos = arg2pos.get (bound))
+		{
+		  /* BOUND previously seen in the parameter list.  */
+		  TREE_PURPOSE (vb) = size_int (*psizpos);
+		  sprintf (specbuf, "$%u", *psizpos);
+		  spec += specbuf;
+		}
+	      else
+		{
+		  /* BOUND doesn't name a parameter (it could be a global
+		     variable or an expression such as a function call).  */
+		  spec += '$';
+		}
+	    }
+	}
+    }
+
+  if (!spec.length ())
+    return NULL_TREE;
+
+  /* Build a single attribute access with the string describing all
+     array arguments and an optional list of any non-parameter VLA
+     bounds in order.  */
+  tree str = build_string (spec.length (), spec.c_str ());
+  tree attrargs = tree_cons (NULL_TREE, str, vblist);
+  tree name = get_identifier ("access");
+  return tree_cons (name, attrargs, NULL_TREE);
+}
+
 /* Handle a "nothrow" attribute; arguments as in
    struct attribute_spec.handler.  */
 
diff --git a/gcc/c-family/c-warn.c b/gcc/c-family/c-warn.c
index c32d8228b5c..fea8885bf35 100644
--- a/gcc/c-family/c-warn.c
+++ b/gcc/c-family/c-warn.c
@@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License
 along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
+#define INCLUDE_STRING
 #include "config.h"
 #include "system.h"
 #include "coretypes.h"
@@ -37,6 +38,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "c-family/c-spellcheck.h"
 #include "calls.h"
 #include "stor-layout.h"
+#include "tree-pretty-print.h"
 
 /* Print a warning if a constant expression had overflow in folding.
    Invoke this function on every expression that the language
@@ -3099,3 +3101,340 @@ warn_for_address_or_pointer_of_packed_member (tree type, tree rhs)
 
   check_and_warn_address_or_pointer_of_packed_member (type, rhs);
 }
+
+/* Return EXPR + 1.  Convenience helper used below.  */
+
+static inline tree
+plus_one (tree expr)
+{
+  tree type = TREE_TYPE (expr);
+  return fold_build2 (PLUS_EXPR, type, expr, build_int_cst (type, 1));
+}
+
+/* Detect and diagnose a mismatch between an attribute access specification
+   on the original declaration of FNDECL and that on the parameters NEWPARMS
+   from its refeclaration.  ORIGLOC is the location of the first declaration
+   (FNDECL's is set to the location of the redeclaration).  */
+
+void
+warn_parm_array_mismatch (location_t origloc, tree fndecl, tree newparms)
+{
+    /* The original parameter list (copied from the original declaration
+       into the current [re]declaration, FNDECL)).  The two are equal if
+       and only if FNDECL is the first declaratation.  */
+  tree curparms = DECL_ARGUMENTS (fndecl);
+  if (!curparms || !newparms || curparms == newparms)
+    return;
+
+  if (TREE_CODE (curparms) != PARM_DECL
+      || TREE_CODE (newparms) != PARM_DECL)
+    return;
+  /* Extract the (possibly empty) attribute access specification from
+     the declaration and its type (it doesn't yet reflect those created
+     in response to NEWPARMS).  */
+  rdwr_map cur_idx;
+  tree fntype = TREE_TYPE (fndecl);
+  init_attr_rdwr_indices (&cur_idx, TYPE_ATTRIBUTES (fntype));
+
+  /* Build a (possibly null) chain of access attributes corresponding
+     to NEWPARMS.  */
+  const bool builtin = fndecl_built_in_p (fndecl);
+  tree newattrs = build_attr_access_from_parms (newparms, builtin);
+
+  /* Extract the (possibly empty) attribute access specification from
+     NEWATTRS.  */
+  rdwr_map new_idx;
+  init_attr_rdwr_indices (&new_idx, newattrs);
+
+  /* If both are empty there's nothing to do.  If at least one isn't
+     empty there may be mismatches, such as between f(T*) and f(T[1]),
+     where the former mapping woud be empty.  */
+  if (cur_idx.is_empty () && new_idx.is_empty ())
+    return;
+
+  /* Create an empty access specification and use it for pointers with
+     no spec of their own.  */
+  attr_access ptr_spec = { };
+
+  /* Iterate over the two lists of function parameters, comparing their
+     respective mappings and diagnosing mismatches.  */
+  unsigned parmpos = 0;
+  for (tree curp = curparms, newp = newparms; curp;
+       curp = TREE_CHAIN (curp), newp = TREE_CHAIN (newp), ++parmpos)
+    {
+      /* Only check pointers and C++ references.  */
+      tree newptype = TREE_TYPE (newp);
+      if (!POINTER_TYPE_P (newptype))
+	continue;
+
+      {
+	/* Skip mismatches in __builtin_va_list that is commonly
+	   an array but that in declarations of built-ins decays
+	   to a pointer.  */
+	if (builtin && TREE_TYPE (newptype) == TREE_TYPE (va_list_type_node))
+	  continue;
+      }
+
+      /* Access specs for the argument on the current (previous) and
+	 new (to replace the current) declarations.  Either may be null,
+	 indicating the parameter is an ordinary pointer with no size
+	 associated with it.  */
+      attr_access *cura = cur_idx.get (parmpos);
+      attr_access *newa = new_idx.get (parmpos);
+
+      if (!newa)
+	{
+	  /* Continue of both parameters are pointers with no size
+	     associated with it.  */
+	  if (!cura)
+	    continue;
+
+	  /* Otherwise point at PTR_SPEC and set its parameter pointer
+	     and number.  */
+	  newa = &ptr_spec;
+	  newa->ptr = newp;
+	  newa->ptrarg = parmpos;
+	}
+      else if (!cura)
+	{
+	  cura = &ptr_spec;
+	  cura->ptr = curp;
+	  cura->ptrarg = parmpos;
+	}
+
+      /* Set if the parameter is [re]declared as a VLA.  */
+      const bool cur_vla_p = cura->size || cura->minsize == HOST_WIDE_INT_M1U;
+      const bool new_vla_p = newa->size || newa->minsize == HOST_WIDE_INT_M1U;
+
+      if (DECL_P (curp))
+	origloc = DECL_SOURCE_LOCATION (curp);
+      else if (EXPR_P (curp) && EXPR_HAS_LOCATION (curp))
+	origloc = EXPR_LOCATION (curp);
+
+      /* The location of the parameter in the current redeclaration.  */
+      location_t newloc = DECL_SOURCE_LOCATION (newp);
+      if (origloc == UNKNOWN_LOCATION)
+	origloc = newloc;
+
+      tree curptype = TREE_TYPE (curp);
+      const std::string newparmstr = newa->array_as_string (newptype);
+      const std::string curparmstr = cura->array_as_string (curptype);
+      if (new_vla_p && !cur_vla_p)
+	{
+	  if (warning_at (newloc, OPT_Wvla_parameter,
+			  "argument %u of type %qs declared as "
+			  "a variable length array",
+			  parmpos + 1, newparmstr.c_str ()))
+	    inform (origloc,
+		    (cura == &ptr_spec
+		     ? G_("previously declared as a pointer %qs")
+		     : G_("previously declared as an ordinary array %qs")),
+		    curparmstr.c_str ());
+	  continue;
+	}
+
+      if (newa == &ptr_spec)
+	{
+	  /* The new declaration uses the pointer form.  Detect mismatches
+	     between the pointer and a previous array or VLA forms.  */
+	  if (cura->minsize == HOST_WIDE_INT_M1U)
+	    {
+	      /* Diagnose a pointer/VLA mismatch.  */
+	      if (warning_at (newloc, OPT_Wvla_parameter,
+			      "argument %u of type %qs declared "
+			      "as a pointer",
+			      parmpos + 1, newparmstr.c_str ()))
+		inform (origloc,
+			"previously declared as a variable length array %qs",
+			curparmstr.c_str ());
+	      continue;
+	    }
+
+	  if (cura->minsize && cura->minsize != HOST_WIDE_INT_M1U)
+	    {
+	      /* Diagnose mismatches between arrays with a constant
+		 bound and pointers.  */
+	      if (warning_at (newloc, OPT_Warray_parameter_,
+			      "argument %u of type %qs declared "
+			      "as a pointer",
+			      parmpos + 1, newparmstr.c_str ()))
+		inform (origloc, "previously declared as an array %qs",
+			curparmstr.c_str ());
+	      continue;
+	    }
+	}
+
+      if (!new_vla_p && cur_vla_p)
+	{
+	  if (warning_at (newloc, OPT_Wvla_parameter,
+			  "argument %u of type %qs declared "
+			  "as an ordinary array",
+			  parmpos + 1, newparmstr.c_str ()))
+	    inform (origloc,
+		    "previously declared as a variable length array %qs",
+		    curparmstr.c_str ());
+	  continue;
+	}
+
+      /* Move on to the next pair of parameters if both of the current
+	 pair are VLAs with a single variable bound that refers to
+	 a parameter at the same position.  */
+      if (newa->size && cura->size
+	  && newa->sizarg != UINT_MAX
+	  && newa->sizarg == cura->sizarg
+	  && newa->minsize == cura->minsize
+	  && !TREE_CHAIN (newa->size) && !TREE_CHAIN (cura->size))
+	continue;
+
+      if (newa->size || cura->size)
+	{
+	  unsigned newunspec, curunspec;
+	  unsigned newbnds = newa->vla_bounds (&newunspec) + newunspec;
+	  unsigned curbnds = cura->vla_bounds (&curunspec) + curunspec;
+
+	  if (newbnds != curbnds)
+	    {
+	      if (warning_n (newloc, OPT_Wvla_parameter, newbnds,
+			     "argument %u of type %qs declared with "
+			     "%u variable bound",
+			     "argument %u of type %qs declared with "
+			     "%u variable bounds",
+			     parmpos + 1, newparmstr.c_str (),
+			     newbnds))
+		inform_n (origloc, curbnds,
+			  "previously declared as %qs with %u variable bound",
+			  "previously declared as %qs with %u variable bounds",
+			  curparmstr.c_str (), curbnds);
+	      continue;
+	    }
+
+	  if (newunspec != curunspec)
+	    {
+	      if (warning_n (newloc, OPT_Wvla_parameter, newunspec,
+			     "argument %u of type %qs declared with "
+			     "%u unspecified variable bound",
+			     "argument %u of type %qs declared with "
+			     "%u unspecified variable bounds",
+			     parmpos + 1, newparmstr.c_str (), newunspec))
+		inform_n (origloc, curunspec,
+			  "previously declared as %qs with %u unspecified "
+			  "variable bound",
+			  "previously declared as %qs with %u unspecified "
+			  "variable bounds",
+			  curparmstr.c_str (), curunspec);
+	      continue;
+	    }
+	}
+
+      /* Iterate over the lists of VLA variable bounds, comparing each
+	 pair for equality, and diagnosing mismatches.  The case of
+	 the lists having different lengths is handled above so at
+	 this point they do .  */
+      for (tree newvbl = newa->size, curvbl = cura->size; newvbl;
+	   newvbl = TREE_CHAIN (newvbl), curvbl = TREE_CHAIN (curvbl))
+	{
+	  tree newpos = TREE_PURPOSE (newvbl);
+	  tree curpos = TREE_PURPOSE (curvbl);
+
+	  tree newbnd = TREE_VALUE (newvbl);
+	  tree curbnd = TREE_VALUE (curvbl);
+
+	  if (newpos == curpos && newbnd == curbnd)
+	    /* In the expected case when both bounds either refer to
+	       the same positional parameter or when neither does,
+	       and both are the same expression they are necessarily
+	       the same.  */
+	    continue;
+
+	  const char* const newbndstr =
+	    newbnd ? print_generic_expr_to_str (newbnd) : "*";
+	  const char* const curbndstr =
+	    curbnd ? print_generic_expr_to_str (curbnd) : "*";
+
+	  if (!newpos != !curpos
+	      || (newpos && !tree_int_cst_equal (newpos, curpos)))
+	    {
+	      /* Diagnose a mismatch between a specified VLA bound and
+		 an unspecified one.  This can only happen in the most
+		 significant bound.
+
+		 Distinguish between the common case of bounds that are
+		 other function parameters such as in
+		   f (int n, int[n]);
+		 and others.  */
+
+	      gcc_rich_location richloc (newloc);
+	      bool warned;
+	      if (newpos)
+		{
+		  /* Also underline the VLA bound argument.  */
+		  richloc.add_range (DECL_SOURCE_LOCATION (newbnd));
+		  warned = warning_at (&richloc, OPT_Wvla_parameter,
+				       "argument %u of type %qs declared "
+				       "with mismatched bound argument %E",
+				       parmpos + 1, newparmstr.c_str (),
+				       plus_one (newpos));
+		}
+	      else
+		warned = warning_at (&richloc, OPT_Wvla_parameter,
+				     "argument %u of type %qs declared "
+				     "with mismatched bound %<%s%>",
+				     parmpos + 1, newparmstr.c_str (),
+				     newbndstr);
+
+	      if (warned)
+		{
+		  gcc_rich_location richloc (origloc);
+		  if (curpos)
+		    {
+		      /* Also underline the VLA bound argument.  */
+		      richloc.add_range (DECL_SOURCE_LOCATION (curbnd));
+		      inform (&richloc, "previously declared as %qs with "
+			      "bound argument %E",
+			      curparmstr.c_str (), plus_one (curpos));
+		    }
+		  else
+		    inform (&richloc, "previously declared as %qs with bound "
+			    "%<%s%>", curparmstr.c_str (), curbndstr);
+
+		  continue;
+		}
+	    }
+
+	  if (!newpos && newbnd && curbnd)
+	    {
+	      /* The VLA bounds don't refer to other function parameters.
+		 Compare them lexicographically to detect gross mismatches
+		 such as between T[foo()] and T[bar()].  */
+	      if (operand_equal_p (newbnd, curbnd, OEP_LEXICOGRAPHIC))
+		continue;
+
+	      if (warning_at (newloc, OPT_Wvla_parameter,
+			      "argument %u of type %qs declared with "
+			      "mismatched bound %<%s%>",
+			      parmpos + 1, newparmstr.c_str (),
+			      newbndstr))
+		inform (origloc, "previously declared as %qs with bound %qs",
+			curparmstr.c_str (), curbndstr);
+	      continue;
+	    }
+	}
+
+      if (newa->minsize == cura->minsize
+	  || (((newa->minsize == 0 && newa->mode != attr_access::deferred)
+	       || (cura->minsize == 0 && cura->mode != attr_access::deferred))
+	      && newa != &ptr_spec
+	      && cura != &ptr_spec))
+	continue;
+
+      if (!newa->static_p && !cura->static_p && warn_array_parameter < 2)
+	/* Avoid warning about mismatches in ordinary (non-static) arrays
+	   at levels below 2.  */
+	continue;
+
+      if (warning_at (newloc, OPT_Warray_parameter_,
+		      "argument %u of type %qs with mismatched bound",
+		      parmpos + 1, newparmstr.c_str ()))
+	inform (origloc, "previously declared as %qs", curparmstr.c_str ());
+    }
+}
diff --git a/gcc/calls.c b/gcc/calls.c
index 72f89011584..400dd4fbc92 100644
--- a/gcc/calls.c
+++ b/gcc/calls.c
@@ -2210,13 +2210,13 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
 
   bitmap_obstack_release (NULL);
 
+  tree fntypeattrs = TYPE_ATTRIBUTES (fntype);
   /* Extract attribute alloc_size from the type of the called expression
      (which could be a function or a function pointer) 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 = lookup_attribute ("alloc_size",
-					  TYPE_ATTRIBUTES (fntype)))
+  if (tree alloc_size = lookup_attribute ("alloc_size", fntypeattrs))
     {
       tree args = TREE_VALUE (alloc_size);
       alloc_idx[0] = TREE_INT_CST_LOW (TREE_VALUE (args)) - 1;
@@ -2229,7 +2229,7 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
 
   /* Map of attribute accewss specifications for function arguments.  */
   rdwr_map rdwr_idx;
-  init_attr_rdwr_indices (&rdwr_idx, fntype);
+  init_attr_rdwr_indices (&rdwr_idx, fntypeattrs);
 
   /* I counts args in order (to be) pushed; ARGPOS counts in order written.  */
   for (argpos = 0; argpos < num_actuals; i--, argpos++)
diff --git a/gcc/testsuite/gcc.dg/attr-access-read-write-2.c b/gcc/testsuite/gcc.dg/attr-access-read-write-2.c
index c2ac6c344a5..deeee736eb8 100644
--- a/gcc/testsuite/gcc.dg/attr-access-read-write-2.c
+++ b/gcc/testsuite/gcc.dg/attr-access-read-write-2.c
@@ -22,12 +22,12 @@ int RW (1) grdwr1_wr1 (void*, void*);           // { dg-message "previous declar
 int WO (1) grdwr1_wr1 (void*, void*);         // { dg-warning "attribute 'access\\(write_only, 1\\)' mismatch with mode 'read_write'" }
 
 
-int RW (1) RW (1, 2) frdwr1_rdwr1_1 (void*, int);   // { dg-warning "attribute 'access\\(read_write, 1, 2\\)' positional argument 2 conflicts with previous designation" }
+int RW (1) RW (1, 2) frdwr1_rdwr1_1 (void*, int);   // { dg-warning "attribute 'access\\(read_write, 1, 2\\)' positional argument 2 missing in previous designation" }
 
 int RW (1, 2) RW (1) frdwr1_1_rdwr1 (void*, int);   // { dg-warning "attribute 'access\\(read_write, 1\\)' missing positional argument 2 provided in previous designation" }
 
 int RW (1)    grdwr1_rdwr1_1 (void*, int);   // { dg-message "previous declaration here" }
-int RW (1, 2) grdwr1_rdwr1_1 (void*, int);   // { dg-warning "attribute 'access\\(read_write, 1, 2\\)' positional argument 2 conflicts with previous designation" }
+int RW (1, 2) grdwr1_rdwr1_1 (void*, int);   // { dg-warning "attribute 'access\\(read_write, 1, 2\\)' positional argument 2 missing in previous designation" }
 
 
 typedef int *P;
diff --git a/gcc/tree-ssa-uninit.c b/gcc/tree-ssa-uninit.c
index 2f0ff724cde..a1ea1b6c532 100644
--- a/gcc/tree-ssa-uninit.c
+++ b/gcc/tree-ssa-uninit.c
@@ -472,7 +472,7 @@ maybe_warn_pass_by_reference (gimple *stmt, wlimits &wlims)
 
   /* Map of attribute access specifications for function arguments.  */
   rdwr_map rdwr_idx;
-  init_attr_rdwr_indices (&rdwr_idx, fntype);
+  init_attr_rdwr_indices (&rdwr_idx, TYPE_ATTRIBUTES (fntype));
 
   tree argtype;
   unsigned argno = 0;

^ permalink raw reply	[flat|nested] 33+ messages in thread

* [PATCH 2/5] C front end support to detect out-of-bounds accesses to array parameters
  2020-07-29  1:13 [PATCH 0/5] add checking of function array parameters (PR 50584) Martin Sebor
  2020-07-29  1:16 ` [PATCH 1/5] infrastructure to detect out-of-bounds accesses to array parameters Martin Sebor
@ 2020-07-29  1:19 ` Martin Sebor
  2020-07-29 18:12   ` Joseph Myers
  2020-07-29  1:20 ` [PATCH 0/5] add checking of function array parameters (PR 50584) Martin Sebor
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 33+ messages in thread
From: Martin Sebor @ 2020-07-29  1:19 UTC (permalink / raw)
  To: gcc-patches

[-- Attachment #1: Type: text/plain, Size: 835 bytes --]

Patch 2 adds support for the internal attribute access specification
for array/VLA parameters to the C front end and two new options to
control warnings.

The attribute is added in two steps: first, push_parm_decl adds a new
attribute "arg spec" to each array parameter with a string describing
its form and a chain of VLA bounds, and next start_function, after
checking for mismatches with prior declarations, collects these for
each function definitionm, creates an attribute access specification
from them, and installs it in the declaration.
c_parser_declaration_or_fndef does the same for function declarations
that aren't definitions.  The two-step process via attribute "arg spec"
is necessary to associate VLA bounds with VLA parameters, and because
function parameters are transient (they're not available in the middle
end).

[-- Attachment #2: gcc-50584-2.diff --]
[-- Type: text/x-patch, Size: 49432 bytes --]

[2/5] - C front end support to detect out-of-bounds accesses to array parameters.

gcc/c-family/ChangeLog:

	PR c/50584
	* c-common.h (warn_parm_array_mismatch): Declare new function.
	(has_attribute): Move declaration of an existing function.
	(build_attr_access_from_parms): Declare new function.
	* c.opt (-Warray-parameter, -Wvla-parameter): New options.

gcc/c/ChangeLog:

	PR c/50584
	* c-decl.c (lookup_last_decl): Define new function.
	(c_decl_attributes): Call it.
	(start_decl): Add argument and use it.
	(finish_decl): Call build_attr_access_from_parms and decl_attributes.
	(get_parm_array_spec): Define new function.
	(push_parm_decl): Call get_parm_array_spec.
	(start_function): Call warn_parm_array_mismatch.  Build attribute
	access and add it to current function.
	* c-parser.c (c_parser_declaration_or_fndef): Diagnose mismatches
	in forms of array parameters.
	* c-tree.h (start_decl): Add argument.

gcc/ChangeLog:

	PR c/50584
	* calls.c (initialize_argument_information): Remove assertion.
	* doc/invoke.texi (-Warray-parameter, -Wvla-parameter): Document.
	* tree-pretty-print.c (dump_generic_node): Correct handling of
	qualifiers.

gcc/testsuite/ChangeLog:

	PR c/50584
	* c-c++-common/Warray-bounds-6.c: Correct C++ declaration, adjust
	text of expected diagnostics.
	* gcc.dg/Wbuiltin-declaration-mismatch-9.c: Prune expected warning.
	* gcc.dg/Warray-parameter-2.c: New test.
	* gcc.dg/Warray-parameter-3.c: New test.
	* gcc.dg/Warray-parameter-4.c: New test.
	* gcc.dg/Warray-parameter.c: New test.
	* gcc.dg/Wvla-parameter-2.c: New test.
	* gcc.dg/Wvla-parameter.c: New test.

diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index 61627264e1e..8070ae7f0d1 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -1319,6 +1319,7 @@ extern void c_do_switch_warnings (splay_tree, location_t, tree, tree, bool);
 extern void warn_for_omitted_condop (location_t, tree);
 extern bool warn_for_restrict (unsigned, tree *, unsigned);
 extern void warn_for_address_or_pointer_of_packed_member (tree, tree);
+extern void warn_parm_array_mismatch (location_t, tree, tree);
 
 /* Places where an lvalue, or modifiable lvalue, may be required.
    Used to select diagnostic messages in lvalue_error and
@@ -1373,6 +1374,8 @@ extern tree find_tm_attribute (tree);
 extern const struct attribute_spec::exclusions attr_cold_hot_exclusions[];
 extern const struct attribute_spec::exclusions attr_noreturn_exclusions[];
 extern tree handle_noreturn_attribute (tree *, tree, tree, int, bool *);
+extern bool has_attribute (location_t, tree, tree, tree (*)(tree));
+extern tree build_attr_access_from_parms (tree, bool);
 
 /* In c-format.c.  */
 extern bool valid_format_string_type_p (tree);
@@ -1401,8 +1404,6 @@ extern void maybe_suggest_missing_token_insertion (rich_location *richloc,
 						   location_t prev_token_loc);
 extern tree braced_lists_to_strings (tree, tree);
 
-extern bool has_attribute (location_t, tree, tree, tree (*)(tree));
-
 #if CHECKING_P
 namespace selftest {
   /* Declarations for specific families of tests within c-family,
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 2b1aca16eb4..6f3997405a1 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -338,6 +338,14 @@ Warray-bounds=
 LangEnabledBy(C ObjC C++ LTO ObjC++,Wall,1,0)
 ; in common.opt
 
+Warray-parameter
+C ObjC C++ ObjC++ Warning Alias(Warray-parameter=, 2, 0)
+Warn about mismatched declarations of array parameters and unsafe accesses to them.
+
+Warray-parameter=
+C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_array_parameter) IntegerRange(0, 2) LangEnabledBy(C ObjC C++ ObjC++,Wall, 2, 0) Warning
+Warn about mismatched declarations of array parameters and unsafe accesses to them.
+
 Wzero-length-bounds
 C ObjC C++ ObjC++ Var(warn_zero_length_bounds) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall)
 Warn about accesses to interior zero-length array members.
@@ -1253,6 +1261,10 @@ Wno-vla-larger-than
 C ObjC C++ LTO ObjC++ Alias(Wvla-larger-than=,18446744073709551615EiB,none) Warning
 Disable Wvla-larger-than= warning.  Equivalent to Wvla-larger-than=<SIZE_MAX> or larger.
 
+Wvla-parameter
+C ObjC C++ ObjC++ Var(warn_vla_parameter) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall)
+Warn about mismatched declarations of VLA parameters.
+
 Wvolatile
 C++ ObjC++ Var(warn_volatile) Warning
 Warn about deprecated uses of volatile qualifier.
diff --git a/gcc/c/c-decl.c b/gcc/c/c-decl.c
index 5d6b504fe78..5bbcee2dc7b 100644
--- a/gcc/c/c-decl.c
+++ b/gcc/c/c-decl.c
@@ -25,6 +25,7 @@ along with GCC; see the file COPYING3.  If not see
    line numbers.  For example, the CONST_DECLs for enum values.  */
 
 #include "config.h"
+#define INCLUDE_STRING
 #define INCLUDE_UNIQUE_PTR
 #include "system.h"
 #include "coretypes.h"
@@ -4967,6 +4968,17 @@ groktypename (struct c_type_name *type_name, tree *expr,
   return type;
 }
 
+/* Looks up the most recent pushed declaration corresponding to DECL.  */
+
+static tree
+lookup_last_decl (tree decl)
+{
+  tree last_decl = lookup_name (DECL_NAME (decl));
+  if (!last_decl)
+    last_decl = lookup_name_in_scope (DECL_NAME (decl), external_scope);
+  return last_decl;
+}
+
 /* Wrapper for decl_attributes that adds some implicit attributes
    to VAR_DECLs or FUNCTION_DECLs.  */
 
@@ -4995,10 +5007,7 @@ c_decl_attributes (tree *node, tree attributes, int flags)
      so far so that attributes on the current declaration that's
      about to be pushed that conflict with the former can be detected,
      diagnosed, and rejected as appropriate.  */
-  tree last_decl = lookup_name (DECL_NAME (*node));
-  if (!last_decl)
-    last_decl = lookup_name_in_scope (DECL_NAME (*node), external_scope);
-
+  tree last_decl = lookup_last_decl (*node);
   return decl_attributes (node, attributes, flags, last_decl);
 }
 
@@ -5008,6 +5017,8 @@ c_decl_attributes (tree *node, tree attributes, int flags)
    have been parsed, before parsing the initializer if any.
    Here we create the ..._DECL node, fill in its type,
    and put it on the list of decls for the current context.
+   When nonnull, set *LASTLOC to the location of the prior declaration
+   of the same entity if one exists.
    The ..._DECL node is returned as the value.
 
    Exception: for arrays where the length is not specified,
@@ -5020,7 +5031,7 @@ c_decl_attributes (tree *node, tree attributes, int flags)
 
 tree
 start_decl (struct c_declarator *declarator, struct c_declspecs *declspecs,
-	    bool initialized, tree attributes)
+	    bool initialized, tree attributes, location_t *lastloc /* = NULL */)
 {
   tree decl;
   tree tem;
@@ -5038,6 +5049,10 @@ start_decl (struct c_declarator *declarator, struct c_declspecs *declspecs,
   if (!decl || decl == error_mark_node)
     return NULL_TREE;
 
+  if (tree lastdecl = lastloc ? lookup_last_decl (decl) : NULL_TREE)
+    if (lastdecl != error_mark_node)
+      *lastloc = DECL_SOURCE_LOCATION (lastdecl);
+
   if (expr)
     add_stmt (fold_convert (void_type_node, expr));
 
@@ -5475,6 +5490,14 @@ finish_decl (tree decl, location_t init_loc, tree init,
 	  if (asmspec && VAR_P (decl) && C_DECL_REGISTER (decl))
 	    DECL_HARD_REGISTER (decl) = 1;
 	  rest_of_decl_compilation (decl, true, 0);
+
+	  if (TREE_CODE (decl) == FUNCTION_DECL)
+	    {
+	      tree parms = DECL_ARGUMENTS (decl);
+	      const bool builtin = fndecl_built_in_p (decl);
+	      if (tree access = build_attr_access_from_parms (parms, !builtin))
+		decl_attributes (&decl, access, 0);
+	    }
 	}
       else
 	{
@@ -5627,6 +5650,123 @@ grokparm (const struct c_parm *parm, tree *expr)
   return decl;
 }
 
+/* Return attribute "arg spec" corresponding to an array/VLA parameter
+   DECL described by PARM, concatenated onto attributes ATTRS.  No "arg
+   spec" is created for parameters of pointer types, making a distinction
+   between T(*)[N] (or, equivalently, T[][N]) and the T[M][N] form, all
+   of which have the same type and are represented the same, but only
+   the last of which gets an "arg spec" describing the most significant
+   bound M.  */
+
+static tree
+get_parm_array_spec (const struct c_parm *parm, tree decl, tree attrs)
+{
+  std::string spec;
+
+  tree type = TREE_TYPE (decl);
+  bool pointer = false;
+
+  /* A list of VLA variable bounds or null if not specified.  */
+  tree vbchain = NULL_TREE;
+  if (parm->declarator->kind == cdk_array)
+    {
+      /* Create a string representation for the bounds of the array/VLA.  */
+      spec = '[';
+      for (c_declarator *pd = parm->declarator, *next; pd; pd = next)
+	{
+	  type = TREE_TYPE (type);
+
+	  if (pd->kind != cdk_array)
+	    break;
+
+	  next = pd->declarator;
+
+	  if (pd->u.array.vla_unspec_p)
+	    {
+	      /* Each unspecified bound is represented by a star.  There
+		 can be any number of these in a declaration (but none in
+		 a definition).  */
+	      spec += '*';
+	      continue;
+	    }
+
+	  tree nelts = pd->u.array.dimen;
+	  if (!nelts)
+	    {
+	      /* Ordinary array of unspecified size is represented by
+		 a space.  There can be at most one for the most significant
+		 bound.  */
+	      spec += ' ';
+	      break;
+	    }
+
+	  if (TREE_CODE (nelts) == INTEGER_CST)
+	    {
+	      /* Skip all constant bounds except the most significant
+		 one.  The interior ones are included in the array type.  */
+	      if (next && next->kind == cdk_array)
+		continue;
+
+	      if (!tree_fits_uhwi_p (nelts))
+		/* Bail completely on invalid bounds.  */
+		return attrs;
+
+	      char buf[40];
+	      const char *code = pd->u.array.static_p ? "s" : "";
+	      unsigned HOST_WIDE_INT n = tree_to_uhwi (nelts);
+	      sprintf (buf, "%s%llu", code, (unsigned long long)n);
+	      spec += buf;
+	      pointer = next->kind == cdk_pointer;
+	      break;
+	    }
+
+	  /* Each variable VLA bound is represented by a dollar sign.  */
+	  spec += '$';
+	  vbchain = tree_cons (NULL_TREE, nelts, vbchain);
+	}
+      spec += ']';
+    }
+  else if (parm->declarator->kind == cdk_id)
+    {
+      /* Extract the upper bound from a parameter of an array type.  */
+      if (parm->specs->type
+	  && TREE_CODE (parm->specs->type) == ARRAY_TYPE
+	  && TYPE_DOMAIN (parm->specs->type)
+	  && TYPE_MAX_VALUE (TYPE_DOMAIN (parm->specs->type)))
+	{
+	  tree max = TYPE_MAX_VALUE (TYPE_DOMAIN (parm->specs->type));
+	  if (tree_fits_shwi_p (max))
+	    {
+	      /* The upper bound is the value of the largest valid index.  */
+	      HOST_WIDE_INT n = tree_to_shwi (max) + 1;
+	      char buf[40];
+	      sprintf (buf, "[%lu]", (unsigned long)n);
+	      spec += buf;
+	    }
+	}
+    }
+  else if (parm->declarator->kind == cdk_pointer)
+    {
+      /* Do nothing for the common case of a pointer.  The fact that
+	 the parameter is one can be deduced from the absence of
+	 an arg spec for it.  */
+      pointer = true;
+    }
+
+  if (spec.empty ())
+    return attrs;
+
+  if (pointer) // type && TREE_CODE (type) == ARRAY_TYPE)
+    /* Avoid pointer to array parameters of the T(*)[X] form, as opposed
+       to arrays declared using the T[Y][Z] form.  */
+    return attrs;
+
+  tree acsstr = build_string (spec.length () + 1, spec.c_str ());
+  tree args = tree_cons (NULL_TREE, acsstr, vbchain);
+  tree name = get_identifier ("arg spec");
+  return tree_cons (name, args, attrs);
+}
+
 /* Given a parsed parameter declaration, decode it into a PARM_DECL
    and push that on the current scope.  EXPR is a pointer to an
    expression that needs to be evaluated for the side effects of array
@@ -5636,12 +5776,12 @@ void
 push_parm_decl (const struct c_parm *parm, tree *expr)
 {
   tree attrs = parm->attrs;
-  tree decl;
-
-  decl = grokdeclarator (parm->declarator, parm->specs, PARM, false, NULL,
-			 &attrs, expr, NULL, DEPRECATED_NORMAL);
+  tree decl = grokdeclarator (parm->declarator, parm->specs, PARM, false, NULL,
+			      &attrs, expr, NULL, DEPRECATED_NORMAL);
   if (decl && DECL_P (decl))
     DECL_SOURCE_LOCATION (decl) = parm->loc;
+
+  attrs = get_parm_array_spec (parm, decl, attrs);
   decl_attributes (&decl, attrs, 0);
 
   decl = pushdecl (decl);
@@ -9357,12 +9498,22 @@ start_function (struct c_declspecs *declspecs, struct c_declarator *declarator,
 		 "%qD is normally a non-static function", decl1);
     }
 
+  tree parms = current_function_arg_info->parms;
+  if (old_decl)
+    {
+      location_t origloc = DECL_SOURCE_LOCATION (old_decl);
+      warn_parm_array_mismatch (origloc, old_decl, parms);
+    }
+
   /* Record the decl so that the function name is defined.
      If we already have a decl for this name, and it is a FUNCTION_DECL,
      use the old decl.  */
 
   current_function_decl = pushdecl (decl1);
 
+  if (tree access = build_attr_access_from_parms (parms, false))
+    decl_attributes (&current_function_decl, access, 0, old_decl);
+
   push_scope ();
   declare_parm_level ();
 
diff --git a/gcc/c/c-parser.c b/gcc/c/c-parser.c
index 7961cbc98bb..f5d18195c14 100644
--- a/gcc/c/c-parser.c
+++ b/gcc/c/c-parser.c
@@ -68,6 +68,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "intl.h"
 #include "c-family/name-hint.h"
 #include "tree-iterator.h"
+#include "tree-pretty-print.h"
 #include "memmodel.h"
 #include "c-family/known-headers.h"
 
@@ -2296,13 +2297,11 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok,
 		  c_parser_skip_to_end_of_block_or_statement (parser);
 		  return;
 		}
-	      tree d = start_decl (declarator, specs, false,
-				   chainon (postfix_attrs,
-					    all_prefix_attrs));
-	      if (d
-		  && TREE_CODE (d) == FUNCTION_DECL
-		  && DECL_ARGUMENTS (d) == NULL_TREE
-		  && DECL_INITIAL (d) == NULL_TREE)
+
+	      location_t lastloc = UNKNOWN_LOCATION;
+	      tree attrs = chainon (postfix_attrs, all_prefix_attrs);
+	      tree d = start_decl (declarator, specs, false, attrs, &lastloc);
+	      if (d && TREE_CODE (d) == FUNCTION_DECL)
 		{
 		  /* Find the innermost declarator that is neither cdk_id
 		     nor cdk_attrs.  */
@@ -2331,10 +2330,18 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok,
 			gcc_unreachable ();
 		      }
 
-		  /* If it exists and is cdk_function, use its parameters.  */
+		  /* If it exists and is cdk_function declaration whose
+		     arguments have not been set yet, use its arguments.  */
 		  if (last_non_id_attrs
 		      && last_non_id_attrs->kind == cdk_function)
-		    DECL_ARGUMENTS (d) = last_non_id_attrs->u.arg_info->parms;
+		    {
+		      tree parms = last_non_id_attrs->u.arg_info->parms;
+		      if (DECL_ARGUMENTS (d) == NULL_TREE
+			  && DECL_INITIAL (d) == NULL_TREE)
+			DECL_ARGUMENTS (d) = parms;
+
+		      warn_parm_array_mismatch (lastloc, d, parms);
+		    }
 		}
 	      if (omp_declare_simd_clauses.exists ())
 		{
diff --git a/gcc/c/c-tree.h b/gcc/c/c-tree.h
index 10938cf0857..3adc29dafc4 100644
--- a/gcc/c/c-tree.h
+++ b/gcc/c/c-tree.h
@@ -609,7 +609,7 @@ extern void shadow_tag_warned (const struct c_declspecs *, int);
 extern tree start_enum (location_t, struct c_enum_contents *, tree);
 extern bool start_function (struct c_declspecs *, struct c_declarator *, tree);
 extern tree start_decl (struct c_declarator *, struct c_declspecs *, bool,
-			tree);
+			tree, location_t * = NULL);
 extern tree start_struct (location_t, enum tree_code, tree,
 			  class c_struct_parse_info **);
 extern void store_parm_decls (void);
diff --git a/gcc/calls.c b/gcc/calls.c
index 400dd4fbc92..622417ff74b 100644
--- a/gcc/calls.c
+++ b/gcc/calls.c
@@ -2479,7 +2479,7 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
 	  if (POINTER_TYPE_P (type))
 	    {
 	      access->ptr = args[i].tree_value;
-	      gcc_assert (access->size == NULL_TREE);
+	      // A nonnull ACCESS->SIZE contains VLA bounds.  */
 	    }
 	  else
 	    {
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 7c3cc141c01..91711d23eba 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -5197,6 +5197,7 @@ Options} and @ref{Objective-C and Objective-C++ Dialect Options}.
 
 @gccoptlist{-Waddress   @gol
 -Warray-bounds=1 @r{(only with} @option{-O2}@r{)}  @gol
+-Warray-parameter=2 @r{(C and Objective-C only)} @gol
 -Wbool-compare  @gol
 -Wbool-operation  @gol
 -Wc++11-compat  -Wc++14-compat  @gol
@@ -5248,6 +5249,7 @@ Options} and @ref{Objective-C and Objective-C++ Dialect Options}.
 -Wunused-label     @gol
 -Wunused-value     @gol
 -Wunused-variable  @gol
+-Wvla-parameter @r{(C and Objective-C only)} @gol
 -Wvolatile-register-var  @gol
 -Wzero-length-bounds}
 
@@ -7091,6 +7093,54 @@ pointers. This warning level may give a larger number of
 false positives and is deactivated by default.
 @end table
 
+@item -Warray-parameter
+@itemx -Warray-parameter=@var{n}
+@opindex Wno-array-parameter
+Warn about redeclarations of functions involving arguments of array or
+pointer types of inconsistent kinds or forms, and enable the detection
+of out-of-bounds accesses to such parameters by warnings such as
+@option{-Warray-bounds}.
+
+If the first function declaration uses the array form the bound specified
+in the array is assumed to be the minimum number of elements expected to
+be provided in calls to the function and the maximum number of elements
+accessed by it.  Failing to provide arguments of sufficient size or accessing
+more than the maximum number of elements may be diagnosed by warnings such
+as @option{-Warray-bounds}.  At level 1 the warning diagnoses inconsistencies
+involving array parameters declared using the @code{T[static N]} form.
+
+For example, the warning triggers for the following redeclarations because
+the first one allows an array of any size to be passed to @code{f} while
+the second one with the keyword @code{static} specifies that the array
+argument must have at least four elements.
+
+@smallexample
+void f (int[static 4]);
+void f (int[]);           // warning (inconsistent array form)
+
+void g (void)
+@{
+  int *p = (int *)malloc (4);
+  f (p);                  // warning (array too small)
+  @dots{}
+@}
+@end smallexample
+
+At level 2 the warning also triggers for redeclarations involving any other
+inconsistency in array or pointer argument forms denoting array sizes.
+Pointers and arrays of unspecified bound are considered equivalent and do
+not trigger a warning.
+
+@smallexample
+void g (int*);
+void g (int[]);     // no warning
+void g (int[8]);    // warning (inconsistent array bound)
+@end smallexample
+
+@option{-Warray-parameter=2} is included in @option{-Wall}.  The
+@option{-Wvla-parameter} option triggers warnings for similar inconsistencies
+involving Variable Length Array arguments.
+
 @item -Wattribute-alias=@var{n}
 @itemx -Wno-attribute-alias
 @opindex Wattribute-alias
@@ -8514,6 +8564,44 @@ See also @option{-Walloca-larger-than=@var{byte-size}}.
 Disable @option{-Wvla-larger-than=} warnings.  The option is equivalent
 to @option{-Wvla-larger-than=}@samp{SIZE_MAX} or larger.
 
+@item -Wvla-parameter
+@opindex Wno-vla-parameter
+Warn about redeclarations of functions involving arguments of Variable
+Length Array types of inconsistent kinds or forms, and enable the detection
+of out-of-bounds accesses to such parameters by warnings such as
+@option{-Warray-bounds}.
+
+If the first function declaration uses the VLA form the bound specified
+in the array is assumed to be the minimum number of elements expected to
+be provided in calls to the function and the maximum number of elements
+accessed by it.  Failing to provide arguments of sufficient size or
+accessing more than the maximum number of elements may be diagnosed.
+
+For example, the warning triggers for the following redeclarations because
+the first one allows an array of any size to be passed to @code{f} while
+the second one specifies that the array argument must have at least @code{n}
+elements.  In addition, calling @code{f} with the assotiated VLA bound
+parameter in excess of the actual VLA bound triggers a warning as well.
+
+@smallexample
+void f (int n, int[n]);
+void f (int, int[]);     // warning: argument 2 previously declared as a VLA
+
+void g (int n)
+@{
+    if (n > 4)
+      return;
+    int a[n];
+    f (sizeof a, a);     // warning: access to a by f may be out of bounds
+  @dots{}
+@}
+
+@end smallexample
+
+@option{-Wvla-parameter} is included in @option{-Wall}.  The
+@option{-Warray-parameter} option triggers warnings for similar problems
+involving ordinary array arguments.
+
 @item -Wvolatile-register-var
 @opindex Wvolatile-register-var
 @opindex Wno-volatile-register-var
diff --git a/gcc/testsuite/c-c++-common/Warray-bounds-6.c b/gcc/testsuite/c-c++-common/Warray-bounds-6.c
index 6611d5cd916..5e787360314 100644
--- a/gcc/testsuite/c-c++-common/Warray-bounds-6.c
+++ b/gcc/testsuite/c-c++-common/Warray-bounds-6.c
@@ -1,8 +1,12 @@
-/* PR tree-optimization/86614 */
-/* { dg-do compile } */
-/* { dg-options "-O2 -Warray-bounds" } */
+/* PR tree-optimization/86614 - duplicate -Warray-bounds for a strncpy
+   call with out-of-bounds offset
+   { dg-do compile }
+   { dg-options "-O2 -Warray-bounds" } */
 
-extern char *strncpy (char *, const char *, __SIZE_TYPE__);
+#if __cplusplus
+extern "C"
+#endif
+char *strncpy (char *, const char *, __SIZE_TYPE__);
 
 void sink (void *);
 
@@ -12,7 +16,8 @@ void g (const char *s, unsigned n)
 {
   int i = (char *)a[1].b - (char *)a + 1;
   char *d = a[1].b;
-  /* Ensure the same bug is not diagnosed more than once.  */
-  strncpy (d + i, s, n);	/* { dg-warning "array subscript \[0-9]+ is outside array bounds of" } */
-				/* { dg-bogus "offset \[0-9]+ is out of the bounds \\\[0, \[0-9]+\\\] of object 'a' with type" "" { target *-*-* } .-1 } */
+  /* Verify the bug is diagnosed exactly once, using either form
+     of the warning.  */
+  strncpy (d + i, s, n);	/* { dg-warning "array subscript \[0-9]+ is outside array bounds|offset \[0-9]+ is out of the bounds" } */
+				/* { dg-bogus "offset \[0-9]+ is out of the bounds|array subscript \[0-9]+ is outside array bounds" "" { target *-*-* } .-1 } */
 }
diff --git a/gcc/testsuite/gcc.dg/Warray-parameter-2.c b/gcc/testsuite/gcc.dg/Warray-parameter-2.c
new file mode 100644
index 00000000000..88f20e203ce
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Warray-parameter-2.c
@@ -0,0 +1,45 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+   { dg-do compile }
+   { dg-options "-Wall" } */
+
+// Reduced from Glibc.
+
+typedef struct FILE FILE;
+
+int vfprintf (FILE*, const char*, __builtin_va_list);
+int vfprintf (FILE*, const char*, __builtin_va_list);   // { dg-bogus "-Warray-parameter" }
+int vfprintf (FILE*, const char*, __builtin_va_list);
+
+int vfscanf (FILE*, const char*, __builtin_va_list);
+int vfscanf (FILE*, const char*, __builtin_va_list);    // { dg-bogus "-Warray-parameter" }
+int vfscanf (FILE*, const char*, __builtin_va_list);
+
+
+/* Verify that mismatches in array/to pointer to va_list are still
+   diagnosed.  */
+
+int fva (__builtin_va_list);
+int fva (__builtin_va_list);
+
+int fpva_a1 (__builtin_va_list*);
+int fpva_a1 (__builtin_va_list[1]);     // { dg-warning "\\\[-Warray-parameter" }
+
+int fpva_a_ (__builtin_va_list*);
+int fpva_a_ (__builtin_va_list[]);
+int fpva_a_ (__builtin_va_list*);
+int fpva_a_ (__builtin_va_list[]);
+
+/* Also verify that a mismatch between a pointer and a one-element
+   array are diagnosed.  This is pervasive in Glibc headers but
+   making an exception for it would leave no way to express
+   the requirement that a function take at least one argument
+   by reference.  */
+
+struct __jmp_buf_tag;
+int __sigsetjmp (struct __jmp_buf_tag*, int);
+
+struct __jmp_buf_tag { };
+typedef struct __jmp_buf_tag jmp_buf[1];
+
+int __sigsetjmp (struct __jmp_buf_tag[1], int);   // { dg-warning "\\\[-Warray-parameter" }
diff --git a/gcc/testsuite/gcc.dg/Warray-parameter-3.c b/gcc/testsuite/gcc.dg/Warray-parameter-3.c
new file mode 100644
index 00000000000..dd7248303ff
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Warray-parameter-3.c
@@ -0,0 +1,57 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+   { dg-do compile }
+   { dg-options "-Wall -Warray-parameter=1" } */
+
+/* Verify that at level 1 mismatches in the bounds of ordinary array
+   parameters don't trigger -Warray-parameter.  */
+void fax (int[]);
+void fax (int[0]);
+void fax (int[1]);
+void fax (int[2]);
+void fax (int[3]);
+
+/* Same as above but starting with an array with a specified bound.  */
+void gax (int[3]);
+void gax (int[2]);
+void gax (int[1]);
+void gax (int[0]);
+void gax (int[]);
+
+/* Same for multidimensional arrays.  */
+void fax_y (int[][3]);
+void fax_y (int[0][3]);
+void fax_y (int[1][3]);
+void fax_y (int[2][3]);
+void fax_y (int[3][3]);
+
+/* Same as above but starting with an array with a specified bound.  */
+void gax_y (int[3][5]);
+void gax_y (int[2][5]);
+void gax_y (int[1][5]);
+void gax_y (int[0][5]);
+void gax_y (int[][5]);
+
+/* Exercise VLAs with a mismatch in the bound for an ordinary array.  */
+void fvlax_y (int n, int[][n]);
+void fvlax_y (int n, int[0][n]);
+void fvlax_y (int n, int[1][n]);
+void fvlax_y (int n, int[2][n]);
+void fvlax_y (int n, int[3][n]);
+
+void fvlaxn_y (int n, int[][n]);
+void fvlaxn_y (int n, int[0][n]);
+void fvlaxn_y (int n, int[1][n]);
+void fvlaxn_y (int n, int[2][n]);
+void fvlaxn_y (int n, int[3][n]);
+
+void fvlaxx_y (int[][*]);
+void fvlaxx_y (int[0][*]);
+void fvlaxx_y (int[1][*]);
+void fvlaxx_y (int[2][*]);
+void fvlaxx_y (int[3][*]);
+
+/* Verify that mismatches in the bounds of array parameters declared
+   static do trigger -Warray-parameter.  */
+void fas1 (int[static 1]);    // { dg-message "previously declared as 'int\\\[static 1]'" }
+void fas1 (int[static 2]);    // { dg-warning "\\\[-Warray-parameter=" }
diff --git a/gcc/testsuite/gcc.dg/Warray-parameter-4.c b/gcc/testsuite/gcc.dg/Warray-parameter-4.c
new file mode 100644
index 00000000000..f1c867e6e48
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Warray-parameter-4.c
@@ -0,0 +1,86 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+
+   Verify warnings for multidimensional arrays.
+
+   { dg-do compile }
+   { dg-options "-Wall -Warray-parameter=2" } */
+
+// Verify that equivalent forms don't tigger a warning.
+
+typedef int IA1[1];
+typedef int IA1x_[][1];
+typedef int IA1x0[0][1];
+
+void fia_x1 (int[][1]);
+void fia_x1 (IA1[0]);
+void fia_x1 (IA1x_);
+void fia_x1 (IA1x0);
+void fia_x1 (int[0][1]);
+void fia_x1 (int[static 0][1]);
+
+// Same as above one more time.
+void fia_x1 (int[][1]);
+void fia_x1 (IA1[0]);
+void fia_x1 (IA1x_);
+void fia_x1 (IA1x0);
+void fia_x1 (int[0][1]);
+void fia_x1 (int[static 0][1]);
+
+
+void fia1x1 (int[1][1]);
+void fia1x1 (int[1][1]);
+void fia1x1 (int[static 1][1]);
+
+void fia2x1 (int[2][1]);
+void fia2x1 (int[2][1]);
+void fia2x1 (int[static 2][1]);
+
+void fia1x2 (int[1][2]);
+void fia1x2 (int[1][2]);
+void fia1x2 (int[static 1][2]);
+
+void fia1x1_2x1 (int[1][1]);          // { dg-message "previously declared as 'int\\\[1]\\\[1]'" }
+void fia1x1_2x1 (int[2][1]);          // { dg-warning "\\\[-Warray-parameter" }
+void fia1x1_2x1 (int[static 1][1]);
+void fia1x1_2x1 (int[static 2][1]);   // { dg-warning "\\\[-Warray-parameter" }
+
+
+void fia2x1_1x1 (int[2][1]);          // { dg-message "previously declared as 'int\\\[2]\\\[1]'" }
+void fia2x1_1x1 (int[1][1]);          // { dg-warning "\\\[-Warray-parameter" }
+void fia2x1_1x1 (int[2][1]);
+void fia2x1_1x1 (int[static 1][1]);   // { dg-warning "\\\[-Warray-parameter" }
+void fia2x1_1x1 (int[static 2][1]);
+
+
+extern int n1, n2;
+
+void fca_xn1 (char[][n1]);
+void fca_xn1 (char[0][n1]);
+void fca_xn1 (char[static 0][n1]);
+
+void fca1xn1_2xn1 (char[1][n1]);
+void fca1xn1_2xn1 (char[2][n1]);        // { dg-warning "\\\[-Warray-parameter" }
+void fca1xn1_2xn1 (char[1][n1]);
+void fca1xn1_2xn1 (char[static 1][n1]);
+void fca1xn1_2xn1 (char[static 2][n1]); // { dg-warning "\\\[-Warray-parameter" }
+
+
+/* Exercise VLAs with a mismatch in the bound for an ordinary array.  */
+void fvlax_y (int n, int[][n]);
+void fvlax_y (int n, int[0][n]);
+void fvlax_y (int n, int[1][n]);        // { dg-warning "argument 2 of type 'int\\\[1]\\\[n]' with mismatched bound" }
+void fvlax_y (int n, int[2][n]);        // { dg-warning "argument 2 of type 'int\\\[2]\\\[n]' with mismatched bound" }
+void fvlax_y (int n, int[3][n]);        // { dg-warning "argument 2 of type 'int\\\[3]\\\[n]' with mismatched bound" }
+
+void fvlaxn_y (int n, int[][n]);
+void fvlaxn_y (int n, int[0][n]);
+void fvlaxn_y (int n, int[1][n]);       // { dg-warning "\\\[-Warray-parameter" }
+void fvlaxn_y (int n, int[2][n]);       // { dg-warning "\\\[-Warray-parameter" }
+void fvlaxn_y (int n, int[3][n]);       // { dg-warning "\\\[-Warray-parameter" }
+
+void fvlaxx_y (int[][*]);
+void fvlaxx_y (int[0][*]);
+void fvlaxx_y (int[1][*]);              // { dg-warning "\\\[-Warray-parameter" }
+void fvlaxx_y (int[2][*]);              // { dg-warning "\\\[-Warray-parameter" }
+void fvlaxx_y (int[3][*]);              // { dg-warning "\\\[-Warray-parameter" }
diff --git a/gcc/testsuite/gcc.dg/Warray-parameter.c b/gcc/testsuite/gcc.dg/Warray-parameter.c
new file mode 100644
index 00000000000..dc2f412a7cb
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Warray-parameter.c
@@ -0,0 +1,145 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+
+   Verify that -Warray-parameter diagnoses mismatches in array (and
+   pointer) arrguments between redeclarations of the same function.
+   Also verify that the array/pointer argument form in a mismatched
+   redeclaration doesn't override the form in the initial declaration.
+
+   { dg-do compile }
+   { dg-options "-Wall -Warray-parameter -Wno-vla-paramater" } */
+
+/* Redclarations with the same or equivalent array form should not
+   be dianosed.  T[0] is diagnosed by -Wpedantic for being invalid
+   C so there's little point in also warning for the difference in
+   array form.  */
+void f1ia_ (int[]);
+void f1ia_ (int[]);
+void f1ia_ (int[0]);
+/* Verify the unused attribute still has an effect.  */
+void f1ia_ (int a[0] __attribute__ ((unused))) { }
+void f1ia_ (int[]);
+
+void f1ia_p (int[]);
+void f1ia_p (int*);
+void f1ia_p (int *p  __attribute__ ((unused))) { }
+void f1ia_p (int[]);
+
+void f1p_ia (const int*);
+void f1p_ia (const int[]);
+void f1p_ia (const int *p __attribute__ ((unused))) { }
+void f1p_ia (const int[]);
+
+void f1ia1 (int[1]);
+void f1ia1 (int[1]);
+void f1ia1 (int[2 - 1]);
+
+void f1ias2 (int[static 2]);
+void f1ias2 (int[static 2]);
+void f1ias2 (int[static 1 + 1]);
+void f1ias2 (int a[static 3 - 1]) { (void)&a; }
+
+
+void f1ia1_x (int[1]);          // { dg-message "previously declared as 'int\\\[1]'" }
+void f1ia1_x (int[]);           // { dg-warning "argument 1 of type 'int\\\[]' with mismatched bound" }
+void f1ia1_x (int[]);           // { dg-warning "argument 1 of type 'int\\\[]' with mismatched bound" }
+void f1ia1_x (int[1]);
+void f1ia1_x (int[2]);          // { dg-warning "argument 1 of type 'int\\\[2]' with mismatched bound" }
+void f1ia1_x (int[1]);
+void f1ia1_x (int[3]);          // { dg-warning "argument 1 of type 'int\\\[3]' with mismatched bound" }
+void f1ia1_x (int a[1] __attribute__ ((unused))) { }
+
+
+void f1ias2_s3 (int[static 2]); // { dg-message "previously declared as 'int\\\[static 2]'" }
+void f1ias2_s3 (int[static 3]); // { dg-warning "argument 1 of type 'int\\\[static 3]' with mismatched bound" }
+/* Verify the unused attribute still has an effect and doesn't interfere
+   with the warning.  */
+void f1ias2_s3 (int a[static 3] __attribute__ ((unused))) { }  // { dg-warning "argument 1 of type 'int\\\[static 3]' with mismatched bound" }
+
+
+/* Ordinary T[N] and T[static N] forms are both effectively treated
+   the same but strictly have different meanings so they are diagnosed.
+   It might be worth splitting the warning into two levels and having
+   only the higher level treat the ordinary form as T[static N].  */
+
+void f1ia3_s4 (int[3]);         // { dg-message "previously declared as 'int\\\[3]'" }
+void f1ia3_s4 (int[static 4]);  // { dg-warning "argument 1 of type 'int\\\[static 4]' with mismatched bound" }
+void f1ia3_s4 (int[3]);
+
+
+void f1ias4_5 (int[static 4]);  // { dg-message "previously declared as 'int\\\[static 4]'" }
+void f1ias4_5 (int[5]);         // { dg-warning "argument 1 of type 'int\\\[5]' with mismatched bound" }
+void f1ias4_5 (int[static 4]);
+
+
+void f1ia_1 (int[]);            // { dg-message "previously declared as 'int\\\[]'" }
+void f1ia_1 (int[1]);           // { dg-warning "argument 1 of type 'int\\\[1]' with mismatched bound" }
+void f1ia_1 (int[]);
+
+
+void f1ca_ (char[]);            // { dg-message "previously declared as 'char\\\[]'" }
+void f1ca_ (char[2]);           // { dg-warning "argument 1 of type 'char\\\[2]' with mismatched bound" }
+void f1ca_ (char[]);
+
+
+void f1csp (const short*);      // { dg-message "previously declared as 'const short int\\\*'" }
+void f1csp (const short[3]);    // { dg-warning "argument 1 of type 'const short int\\\[3]' with mismatched bound" }
+void f1csp (const short*);
+
+
+void f1ia2 (int[2]);            // { dg-message "previously declared as 'int\\\[2]'" }
+void f1ia2 (int[1]);            // { dg-warning "argument 1 of type 'int\\\[1]' with mismatched bound" }
+void f1ia2 (int[2]);
+
+
+void f1cvla2 (const volatile long[3]);  // { dg-message "previously declared as 'const volatile long int\\\[3]'" }
+void f1cvla2 (const volatile long[2]);  // { dg-warning "argument 1 of type 'const volatile long int\\\[2]' with mismatched bound" }
+void f1cvla2 (const volatile long[3]);
+void f1cvla2 (const volatile long[restrict 4]); // { dg-warning "argument 1 of type 'const volatile long int\\\[restrict 4]' with mismatched bound" }
+
+
+void f1afa4 (_Atomic float[3]);         // { dg-message "previously declared as an array 'atomic float\\\[3]'" }
+void f1afa4 (_Atomic float*);           // { dg-warning "argument 1 of type 'atomic float\\\*' declared as a pointer" }
+void f1afa4 (_Atomic float[3]);
+
+
+typedef int IA1[1];
+typedef int IA2[2];
+typedef int IA3[3];
+
+void f1IA1_A2 (IA1);            // { dg-message "previously declared as 'int\\\[1]'" }
+void f1IA1_A2 (IA2);            // { dg-warning "argument 1 of type 'int\\\[2]' with mismatched bound" }
+void f1IA1_A2 (IA1);
+void f1IA1_A2 (int[2]);         // { dg-warning "argument 1 of type 'int\\\[2]' with mismatched bound" }
+
+
+void f1IA1_A3 (IA1 ia1);        // { dg-message "previously declared as 'int\\\[1]'" }
+void f1IA1_A3 (IA3 ia3);        // { dg-warning "argument 1 of type 'int\\\[3]' with mismatched bound" }
+void f1IA1_A3 (IA1 ia1);
+
+
+void f1IA2_A3 (IA2 a);          // { dg-message "previously declared as 'int\\\[2]'" }
+void f1IA2_A3 (IA3 a);          // { dg-warning "argument 1 of type 'int\\\[3]' with mismatched bound" }
+void f1IA2_A3 (IA2 a);
+
+
+// Verify multiple array arguments.
+
+void f2a2_a3_3_3 (int[2], int[3]);  // { dg-message "previously declared as 'int\\\[2]'" }
+void f2a2_a3_3_3 (int[2], int[3]);
+void f2a2_a3_3_3 (int[3], int[3]);  // { dg-warning "argument 1 of type 'int\\\[3]' with mismatched bound" }
+
+
+void f2a2_a3_2_4 (int[2], int[3]);  // { dg-message "previously declared as 'int\\\[3]'" }
+void f2a2_a3_2_4 (int[2], int[4]);  // { dg-warning "argument 2 of type 'int\\\[4]' with mismatched bound" }
+
+
+/* Verify that pointers to arrays and arrays of arrays are differentiated
+   the same way as pointers and arrays of other types.  */
+typedef int A[1];
+typedef A *PA;
+
+void fpia1 (A*);                    // { dg-message "previously declared as 'int\\\*\\\[1]'" }
+void fpia1 (A[1]);                  // { dg-warning "argument 1 of type 'int\\\[1]\\\[1]' with mismatched bound" }
+void fpia1 (PA);
+
diff --git a/gcc/testsuite/gcc.dg/Wbuiltin-declaration-mismatch-9.c b/gcc/testsuite/gcc.dg/Wbuiltin-declaration-mismatch-9.c
index f0c1ce33267..da767b87700 100644
--- a/gcc/testsuite/gcc.dg/Wbuiltin-declaration-mismatch-9.c
+++ b/gcc/testsuite/gcc.dg/Wbuiltin-declaration-mismatch-9.c
@@ -10,3 +10,6 @@ void a (void)
 	  ""        /* { dg-warning "passing argument 2 of .sscanf. from incompatible pointer type" } */
 	  );
 }
+
+/* The scanf call may also trigger:
+   { dg-prune-output "-Wstringop-overflow" } */
diff --git a/gcc/testsuite/gcc.dg/Wvla-parameter-2.c b/gcc/testsuite/gcc.dg/Wvla-parameter-2.c
new file mode 100644
index 00000000000..210a81d91c7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wvla-parameter-2.c
@@ -0,0 +1,38 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+
+   Verify the -Wvla-parameter warnings correctly diagnose mismatches
+   between VLA arguments with mismatched bounds in redeclarations of
+   the same function.
+
+   { dg-do compile }
+   { dg-options "-Wall -Wvla-parameter" } */
+
+void fmn_a1n_axn (int n, int[1][n]);            // { dg-message "previously declared as 'int\\\[1]\\\[n]' with 1 variable bound" "note" }
+void fmn_a1n_axn (int n, int[*][n]);            // { dg-warning "argument 2 of type 'int\\\[\\\*]\\\[n]' declared with 2 variable bounds" }
+
+
+void fmn_axn_a2n (int n, int[*][n]);            // { dg-message "previously declared as 'int\\\[\\\*]\\\[n]' with 2 variable bounds" "note" }
+void fmn_axn_a2n (int n, int[2][n]);            // { dg-warning "argument 2 of type 'int\\\[2]\\\[n]' declared with 1 variable bound" }
+
+
+void fmn_amn_axn (int m, int n, int[m][n]);     // { dg-message "previously declared as 'int\\\[m]\\\[n]' with 0 unspecified variable bounds" "note" }
+void fmn_amn_axn (int m, int n, int[*][n]);     // { dg-warning "argument 3 of type 'int\\\[\\\*]\\\[n]' declared with 1 unspecified variable bound" }
+
+// Same as above but a different function name.
+void gmn_amn_axn (int m, int n, int[m][n]);     // { dg-message "previously declared as 'int\\\[m]\\\[n]' with 0 unspecified variable bounds" "note" }
+void gmn_amn_axn (int m, int n, int[*][n]);     // { dg-warning "argument 3 of type 'int\\\[\\\*]\\\[n]' declared with 1 unspecified variable bound" }
+
+
+int n1, n2, n3, n4, n5, n6, n7, n8, n9;
+void f (int[n1][2][n3][4][n5][6][n7][8][n9]);   // { dg-message "previously declared as 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[n5]\\\[6]\\\[n7]\\\[8]\\\[n9]' with 0 unspecified variable bounds" "note" }
+                                                // { dg-message "with 5 variable bounds" "note" { target *-*-* } .-1  }
+void f (int[n1][2][n3][4][n5][6][n7][8][n9]);
+
+void f (int[n1][2][n3][4][n5][6][n7][8][*]);    // { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[n5]\\\[6]\\\[n7]\\\[8]\\\[\\\*]' declared with 1 unspecified variable bound" }
+void f (int[n1][2][n3][4][n5][6][*][8][n9]);   // { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[n5]\\\[6]\\\[\\\*]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" }
+void f (int[n1][2][n3][4][*][6][n7][8][n9]);   // { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[\\\*]\\\[6]\\\[n7]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" }
+void f (int[n1][2][*][4][n5][6][n7][8][n9]);   // { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[\\\*]\\\[4]\\\[n5]\\\[6]\\\[n7]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" }
+void f (int[*][2][n3][4][n5][6][n7][8][n9]);   // { dg-warning "argument 1 of type 'int\\\[\\\*]\\\[2]\\\[n3]\\\[4]\\\[n5]\\\[6]\\\[n7]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" }
+
+void f (int[n1][n2][n3][n4][n5][n6][n7][n8][n9]);   // { dg-warning "argument 1 of type 'int\\\[n1]\\\[n2]\\\[n3]\\\[n4]\\\[n5]\\\[n6]\\\[n7]\\\[n8]\\\[n9]' declared with 9 variable bounds" }
diff --git a/gcc/testsuite/gcc.dg/Wvla-parameter.c b/gcc/testsuite/gcc.dg/Wvla-parameter.c
new file mode 100644
index 00000000000..4dc16a48eb5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wvla-parameter.c
@@ -0,0 +1,136 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+   Verify the -Wvla-parameter warnings correctly diagnose mismatches
+   between VLA and non-VLA arguments in redeclarations of the same
+   function.
+   Also verify that the array/pointer argument form in a mismatched
+   redeclaration doesn't override the form in the initial declaration.
+   { dg-do compile }
+   { dg-options "-Wall -Wvla-parameter" } */
+
+/* Verify that redeclaring an argument as a VLA with an unspecified
+   bound that was first declared as an ordinary array with an unspecified
+   bound triggers a warning.  */
+void f1ia_x (int[]);          // { dg-message "previously declared as an ordinary array 'int\\\[]'" "note" }
+void f1ia_x (int[*]);         // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ia_x (int[]);
+void f1ia_x (int[*]);         // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+/* Also verify that a definition of the same form as the first declaration
+   doesn't trigger a warning and doesn't prevent warnings for subsequent
+   mismatches.  */
+void f1ia_x (int a[]) { (void)&a;}
+void f1ia_x (int[*]);         // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+
+/* Repeat the above but starting with an ordinary array with a constant
+   bound.  */
+void f1ia1x (int[1]);          // { dg-message "previously declared as an ordinary array 'int\\\[1]'" "note" }
+void f1ia1x (int[*]);         // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ia1x (int a[1]) { (void)&a; }
+void f1ia1x (int[1]);
+void f1ia1x (int[*]);         // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+
+void f1ipx (int*);            // { dg-message "previously declared as a pointer 'int\\\*'" "note" }
+void f1ipx (int[*]);          // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ipx (int*);
+void f1ipx (int *p) { (void)&p; }
+void f1ipx (int[*]);          // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ipx (int*);
+
+void f2ipx (int*, int*);      // { dg-message "previously declared as a pointer 'int\\\*'" "note" }
+void f2ipx (int*, int[*]);    // { dg-warning "argument 2 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f2ipx (int*, int*);
+void f2ipx (int*, int[*]);    // { dg-warning "argument 2 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f2ipx (int *p, int *q) { (void)&p; (void)&q; }
+void f2ipx (int*, int[*]);    // { dg-warning "argument 2 of type 'int\\\[\\\*]' declared as a variable length array" }
+
+void f1ias2x (int[static 2]); // { dg-message "previously declared as an ordinary array 'int\\\[static 2]'" }
+void f1ias2x (int[*]);        // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ias2x (int[static 2]);
+void f1ias2x (int[*]);        // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ias2x (int a[static 2]) { (void)&a; }
+void f1ias2x (int[*]);        // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ias2x (int[static 2]);
+
+extern int nelts;
+
+void f1sa_var (short[]);      // { dg-message "previously declared as an ordinary array 'short int\\\[]'" }
+void f1sa_var (short[nelts]); // { dg-warning "argument 1 of type 'short int\\\[nelts]' declared as a variable length array" }
+void f1sa_var (short[]);
+void f1sa_var (short[nelts]); // { dg-warning "argument 1 of type 'short int\\\[nelts]' declared as a variable length array" }
+void f1sa_var (short a[]) { (void)&a; }
+void f1sa_var (short[nelts]); // { dg-warning "argument 1 of type 'short int\\\[nelts]' declared as a variable length array" }
+void f1sa_var (short[]);
+
+void f1sa_expr (int[]);           // { dg-message "previously declared as an ordinary array 'int\\\[]'" }
+void f1sa_expr (int[nelts + 1]);  // { dg-warning "argument 1 of type 'int\\\[nelts \\\+ 1]' declared as a variable length array" }
+void f1sa_expr (int[]);
+void f1sa_expr (int[nelts * 2]);  // { dg-warning "argument 1 of type 'int\\\[nelts \\\* 2]' declared as a variable length array" }
+void f1sa_expr (int a[]) { (void)&a; }
+void f1sa_expr (int[nelts / 3]);  // { dg-warning "argument 1 of type 'int\\\[nelts / 3]' declared as a variable length array" }
+void f1sa_expr (int[]);
+
+extern int f (int);
+
+void f1ia_f (int[]);          // { dg-message "previously declared as an ordinary array 'int\\\[]'" }
+void f1ia_f (int[f (1)]);     // { dg-warning "argument 1 of type 'int\\\[f *\\\(1\\\)]' declared as a variable length array" }
+void f1ia_f (int[]);
+void f1ia_f (int[f (2)]);     // { dg-warning "argument 1 of type 'int\\\[f *\\\(2\\\)]' declared as a variable length array" }
+void f1ia_f (int a[]) { (void)&a; }
+void f1ia_f (int[f (3)]);     // { dg-warning "argument 1 of type 'int\\\[f *\\\(3\\\)]' declared as a variable length array" }
+void f1ia_f (int[f (4)]);     // { dg-warning "argument 1 of type 'int\\\[f *\\\(4\\\)]' declared as a variable length array" }
+void f1ia_f (int[]);
+
+void f1iaf0_f1 (int[f (0)]);  // { dg-message "previously declared as 'int\\\[f *\\\(0\\\)]'" }
+void f1iaf0_f1 (int[f (1)]);  // { dg-warning "argument 1 of type 'int\\\[f *\\\(1\\\)]' declared with mismatched bound" }
+void f1iaf0_f1 (int[f (0)]);
+void f1iaf0_f1 (int[f (1)]);  // { dg-warning "argument 1 of type 'int\\\[f *\\\(1\\\)]' declared with mismatched bound" }
+void f1iaf0_f1 (int a[f (0)]) { (void)&a; }
+void f1iaf0_f1 (int[f (1)]);  // { dg-warning "argument 1 of type 'int\\\[f *\\\(1\\\)]' declared with mismatched bound" }
+void f1iaf0_f1 (int[f (0)]);
+
+void f1la_ (long[]);         // { dg-message "previously declared as an ordinary array 'long int\\\[]'" }
+void f1la_ (long[nelts]);    // { dg-warning "argument 1 of type 'long int\\\[nelts]' declared as a variable length array" }
+void f1la_ (long[]);
+void f1la_ (long a[nelts])   // { dg-warning "argument 1 of type 'long int\\\[nelts]' declared as a variable length array" }
+{ (void)&a; }
+void f1la_ (long[]);
+
+void f2ca_ (int, char[]);     // { dg-message "previously declared as an ordinary array 'char\\\[]'" }
+void f2ca_ (int n, char[n]);  // { dg-warning "argument 2 of type 'char\\\[n]' declared as a variable length array" }
+void f2ca_ (int, char[]);
+void f2ca_ (int n, char a[n]) // { dg-warning "argument 2 of type 'char\\\[n]' declared as a variable length array" }
+{ (void)&n; (void)&a; }
+
+void f2ia1_f (int n, int[n]);     // { dg-message "previously declared as 'int\\\[n]' with bound argument 1" }
+void f2ia1_f (int,   int[f (0)]); // { dg-warning "argument 2 of type 'int\\\[f *\\\(0\\\)]' declared with mismatched bound 'f *\\\(0\\\)'" }
+void f2ia1_f (int m, int[m]);
+void f2ia1_f (int,   int[f (1)]); // { dg-warning "argument 2 of type 'int\\\[f *\\\(1\\\)]' declared with mismatched bound 'f *\\\(1\\\)'" }
+void f2ia1_f (int x, int a[x]) { (void)&x; (void)&a; }
+void f2ia1_f (int,   int[f (2)]);   // { dg-warning "argument 2 of type 'int\\\[f *\\\(2\\\)]' declared with mismatched bound 'f *\\\(2\\\)'" }
+void f2ia1_f (int y, int[y]);
+
+void f2iaf_1 (int,   int[f (0)]); // { dg-message "previously declared as 'int\\\[f *\\\(0\\\)]'" }
+void f2iaf_1 (int n, int[n]);     // { dg-warning "argument 2 of type 'int\\\[n]' declared with mismatched bound argument 1" }
+void f2iaf_1 (int,   int[f (0)]);
+void f2iaf_1 (int m, int[m]);     // { dg-warning "argument 2 of type 'int\\\[m]' declared with mismatched bound argument 1" }
+void f2iaf_1 (int x, int a[f (0)]) { (void)&x; (void)&a; }
+void f2iaf_1 (int y, int[y]);     // { dg-warning "argument 2 of type 'int\\\[y]' declared with mismatched bound argument 1" }
+
+
+void f3ia1 (int n, int, int[n]);  // { dg-message "previously declared as 'int\\\[n]' with bound argument 1" }
+void f3ia1 (int, int n, int[n]);  // { dg-warning "argument 3 of type 'int\\\[n]' declared with mismatched bound argument 2" }
+void f3ia1 (int n, int, int[n]);
+
+
+extern int g (int);
+
+void f1iaf_g (int[f (1)]);    // { dg-message "previously declared as 'int\\\[f *\\\(1\\\)]'" }
+void f1iaf_g (int[g (1)]);    // { dg-warning "argument 1 of type 'int\\\[g *\\\(1\\\)]' declared with mismatched bound" }
+void f1iaf_g (int[f (1)]);
+
+
+void nrf1iaf_g (int[f (1)]);  // { dg-message "previously declared as 'int\\\[f *\\\(1\\\)]'" }
+__attribute__ ((nonnull))
+void nrf1iaf_g (int[g (1)]);  // { dg-warning "argument 1 of type 'int\\\[g *\\\(1\\\)]' declared with mismatched bound" }
+__attribute__ ((noreturn))
+void nrf1iaf_g (int[f (1)]);
diff --git a/gcc/tree-pretty-print.c b/gcc/tree-pretty-print.c
index be1ed906c1d..9729433b8cf 100644
--- a/gcc/tree-pretty-print.c
+++ b/gcc/tree-pretty-print.c
@@ -1693,9 +1693,9 @@ dump_generic_node (pretty_printer *pp, tree node, int spc, dump_flags_t flags,
 	  pp_string (pp, "atomic ");
 	if (quals & TYPE_QUAL_CONST)
 	  pp_string (pp, "const ");
-	else if (quals & TYPE_QUAL_VOLATILE)
+	if (quals & TYPE_QUAL_VOLATILE)
 	  pp_string (pp, "volatile ");
-	else if (quals & TYPE_QUAL_RESTRICT)
+	if (quals & TYPE_QUAL_RESTRICT)
 	  pp_string (pp, "restrict ");
 
 	if (!ADDR_SPACE_GENERIC_P (TYPE_ADDR_SPACE (node)))

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH 0/5] add checking of function array parameters (PR 50584)
  2020-07-29  1:13 [PATCH 0/5] add checking of function array parameters (PR 50584) Martin Sebor
  2020-07-29  1:16 ` [PATCH 1/5] infrastructure to detect out-of-bounds accesses to array parameters Martin Sebor
  2020-07-29  1:19 ` [PATCH 2/5] C front end support " Martin Sebor
@ 2020-07-29  1:20 ` Martin Sebor
  2020-08-13 16:26   ` Jeff Law
  2020-07-29  1:22 ` [PATCH 4/5] - extend -Wstringop-overflow to detect out-of-bounds accesses to array parameters Martin Sebor
  2020-07-29  1:24 ` [PATCH 5/5] extend -Warray-bounds " Martin Sebor
  4 siblings, 1 reply; 33+ messages in thread
From: Martin Sebor @ 2020-07-29  1:20 UTC (permalink / raw)
  To: gcc-patches

[-- Attachment #1: Type: text/plain, Size: 143 bytes --]

Patch 3 adjusts tree-ssa-uninit.c to the changes to attribute access but
has only a cosmetic effect on informational notes in -Wuninitialized.

[-- Attachment #2: gcc-50584-3.diff --]
[-- Type: text/x-patch, Size: 9854 bytes --]

[3/5] - Make use of new attribute access infrastructure in tree-ssa-uninit.c.

gcc/ChangeLog:

	* tree-ssa-uninit.c (maybe_warn_pass_by_reference): Handle attribute
	access internal representation of arrays.

gcc/testsuite/ChangeLog:

	* gcc.dg/uninit-37.c: New test.

diff --git a/gcc/testsuite/gcc.dg/uninit-37.c b/gcc/testsuite/gcc.dg/uninit-37.c
new file mode 100644
index 00000000000..b8c49ad17ce
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/uninit-37.c
@@ -0,0 +1,154 @@
+/* PR middle-end/10138 - warn for uninitialized arrays passed as const arguments
+   Verify that -Wuninitialized and -Wmaybe-uninitialized trigger (or don't)
+   when passing uninitialized variables by reference to functions declared
+   with or without attribute access and with (or without) const qualified
+   arguments of array, VLA, or pointer types.
+   { dg-do compile }
+   { dg-options "-O2 -Wall -ftrack-macro-expansion=0" } */
+
+#define NONE    /* none */
+#define RO(...) __attribute__ ((access (read_only, __VA_ARGS__)))
+#define RW(...) __attribute__ ((access (read_write, __VA_ARGS__)))
+#define WO(...) __attribute__ ((access (write_only, __VA_ARGS__)))
+#define X(...)  __attribute__ ((access (none, __VA_ARGS__)))
+
+#define CONCAT(x, y) x ## y
+#define CAT(x, y)    CONCAT (x, y)
+#define UNIQ(pfx)    CAT (pfx, __LINE__)
+
+extern void sink (void*);
+
+
+#define T1(attr, name, type)			\
+  void UNIQ (CAT (test_, name))(void) {		\
+    extern attr void UNIQ (name)(type);		\
+    int x;					\
+    UNIQ (name)(&x);				\
+    sink (&x);					\
+  }
+
+#define T2(attr, name, types)			\
+  void UNIQ (CAT (test_, name))(void) {		\
+    extern attr void UNIQ (name)(types);	\
+    int x;					\
+    UNIQ (name)(1, &x);				\
+    sink (&x);					\
+  }
+
+
+typedef int IA_[];
+typedef const int CIA_[];
+
+T1 (NONE,   fia_,   IA_);
+T1 (NONE,   fcia_,  CIA_);    // { dg-warning "\\\[-Wmaybe-uninitialized" }
+T1 (RO (1), froia_, IA_);     // { dg-warning "\\\[-Wuninitialized" }
+T1 (RW (1), frwia_, IA_);     // { dg-warning "\\\[-Wmaybe-uninitialized" }
+T1 (WO (1), fwoia_, IA_);
+T1 (X (1),  fxia_,  IA_);
+
+
+typedef int IA1[1];
+typedef const int CIA1[1];
+
+T1 (NONE,   fia1,   IA1);
+T1 (NONE,   fcia1,  CIA1);    // { dg-warning "\\\[-Wmaybe-uninitialized" }
+T1 (RO (1), froia1, IA1);     // { dg-warning "\\\[-Wuninitialized" }
+T1 (RW (1), frwia1, IA1);     // { dg-warning "\\\[-Wmaybe-uninitialized" }
+T1 (WO (1), fwoia1, IA1);
+T1 (X (1),  fxia1,  IA1);
+
+
+#define IARS1  int[restrict static 1]
+#define CIARS1 const int[restrict static 1]
+
+T1 (NONE,   fiars1,   IARS1);
+T1 (NONE,   fciars1,  CIARS1);// { dg-warning "\\\[-Wmaybe-uninitialized" }
+T1 (RO (1), froiars1, IARS1); // { dg-warning "\\\[-Wuninitialized" }
+T1 (RW (1), frwiars1, IARS1); // { dg-warning "\\\[-Wmaybe-uninitialized" }
+T1 (WO (1), fwoiars1, IARS1);
+T1 (X (1),  fxiars1,  IARS1);
+
+
+#define IAS1  int[static 1]
+#define CIAS1 const int[static 1]
+
+T1 (NONE,   fias1,   IAS1);
+T1 (NONE,   fcias1,  CIAS1);   // { dg-warning "\\\[-Wmaybe-uninitialized" }
+T1 (RO (1), froias1, IAS1);    // { dg-warning "\\\[-Wuninitialized" }
+T1 (RW (1), frwias1, IAS1);    // { dg-warning "\\\[-Wmaybe-uninitialized" }
+T1 (WO (1), fwoias1, IAS1);
+T1 (X (1),  fxias1,  IAS1);
+
+
+#define IAX  int[*]
+#define CIAX const int[*]
+
+T1 (NONE,   fiax,   IAX);
+T1 (NONE,   fciax,  CIAX);    // { dg-warning "\\\[-Wmaybe-uninitialized" }
+T1 (RO (1), froiax, IAX);     // { dg-warning "\\\[-Wuninitialized" }
+T1 (RW (1), frwiax, IAX);     // { dg-warning "\\\[-Wmaybe-uninitialized" }
+T1 (WO (1), fwoiax, IAX);
+T1 (X (1),  fxiax,  IAX);
+
+
+#define IAN  int n, int[n]
+#define CIAN int n, const int[n]
+
+T2 (NONE,      fian,   IAN);
+T2 (NONE,      fcian,  CIAN); // { dg-warning "\\\[-Wmaybe-uninitialized" }
+T2 (RO (2, 1), froian, IAN);  // { dg-warning "\\\[-Wuninitialized" }
+T2 (RW (2, 1), frwian, IAN);  // { dg-warning "\\\[-Wmaybe-uninitialized" }
+T2 (WO (2, 1), fwoian, IAN);
+T2 (X (2, 1),  fxian,  IAN);
+
+
+typedef int* IP;
+typedef const int* CIP;
+
+T1 (NONE,   fip,   IP);
+T1 (NONE,   fcip,  CIP);     // { dg-warning "\\\[-Wmaybe-uninitialized" }
+T1 (RO (1), froip, IP);      // { dg-warning "\\\[-Wuninitialized" }
+T1 (RW (1), frwip, IP);      // { dg-warning "\\\[-Wmaybe-uninitialized" }
+T1 (WO (1), fwoip, IP);
+T1 (X (1),  fxip,  IP);
+
+
+/* Verify that the notes printed after the warning mention attribute
+   access only when the attribute is explicitly used in the declaration
+   and not otherwise.  */
+
+void test_note_cst_restrict (void)
+{
+  extern void
+    fccar (const char[restrict]);   // { dg-message "by argument 1 of type 'const char\\\[restrict]' to 'fccar'" "note" }
+
+  char a[1];                  // { dg-message "'a' declared here" "note" }
+  fccar (a);                  // { dg-warning "'a' may be used uninitialized" }
+}
+
+void test_note_vla (int n)
+{
+  extern void
+    fccvla (const char[n]);   // { dg-message "by argument 1 of type 'const char\\\[n]' to 'fccvla'" "note" }
+
+  char a[2];                  // { dg-message "'a' declared here" "note" }
+  fccvla (a);                 // { dg-warning "'a' may be used uninitialized" }
+}
+
+void test_note_ro (void)
+{
+  extern RO (1) void
+    frocar (char[restrict]);  // { dg-message "in a call to 'frocar' declared with attribute 'access \\\(read_only, 1\\\)'" "note" }
+
+  char a[3];                  // { dg-message "'a' declared here" "note" }
+  frocar (a);                 // { dg-warning "'a' is used uninitialized" }
+}
+
+void test_note_rw (void)
+{
+  extern RW (1) void
+    frwcar (char[restrict]);  // { dg-message "in a call to 'frwcar' declared with attribute 'access \\\(read_write, 1\\\)'" "note" }
+
+  char a[4];                  // { dg-message "'a' declared here" "note" }
+  frwcar (a);                 // { dg-warning "'a' may be used uninitialized" }
+}
diff --git a/gcc/tree-ssa-uninit.c b/gcc/tree-ssa-uninit.c
index a1ea1b6c532..349513aeea5 100644
--- a/gcc/tree-ssa-uninit.c
+++ b/gcc/tree-ssa-uninit.c
@@ -18,6 +18,7 @@ You should have received a copy of the GNU General Public License
 along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
+#define INCLUDE_STRING
 #include "config.h"
 #include "system.h"
 #include "coretypes.h"
@@ -470,7 +471,8 @@ maybe_warn_pass_by_reference (gimple *stmt, wlimits &wlims)
      read_only.  */
   const bool save_always_executed = wlims.always_executed;
 
-  /* Map of attribute access specifications for function arguments.  */
+  /* Initialize a map of attribute access specifications for arguments
+     to the function function call.  */
   rdwr_map rdwr_idx;
   init_attr_rdwr_indices (&rdwr_idx, TYPE_ATTRIBUTES (fntype));
 
@@ -486,12 +488,17 @@ maybe_warn_pass_by_reference (gimple *stmt, wlimits &wlims)
 	continue;
 
       tree access_size = NULL_TREE;
-      attr_access *access = rdwr_idx.get (argno - 1);
+      const attr_access* access = rdwr_idx.get (argno - 1);
       if (access)
 	{
 	  if (access->mode == attr_access::none
 	      || access->mode == attr_access::write_only)
 	    continue;
+
+	  if (access->mode == attr_access::deferred
+	      && !TYPE_READONLY (TREE_TYPE (argtype)))
+	    continue;
+
 	  if (save_always_executed && access->mode == attr_access::read_only)
 	    /* Attribute read_only arguments imply read access.  */
 	    wlims.always_executed = true;
@@ -522,45 +529,48 @@ maybe_warn_pass_by_reference (gimple *stmt, wlimits &wlims)
       if (!argbase)
 	continue;
 
-      if (access)
+      if (access && access->mode != attr_access::deferred)
 	{
-	  const char* const mode = (access->mode == attr_access::read_only
-				    ? "read_only" : "read_write");
-	  char attrstr[80];
-	  int n = sprintf (attrstr, "access (%s, %u", mode, argno);
-	  if (access->sizarg < UINT_MAX)
-	    sprintf (attrstr + n, ", %u)", access->sizarg);
-	  else
-	    strcpy (attrstr + n, ")");
+	  const char* const access_str =
+	    TREE_STRING_POINTER (access->to_external_string ());
 
 	  if (fndecl)
 	    {
 	      location_t loc = DECL_SOURCE_LOCATION (fndecl);
-	      inform (loc, "in a call to %qD declared "
-		      "with attribute %<access (%s, %u)%> here",
-		      fndecl, mode, argno);
+	      inform (loc, "in a call to %qD declared with "
+		      "attribute %<%s%> here", fndecl, access_str);
 	    }
 	  else
 	    {
 	      /* Handle calls through function pointers.  */
 	      location_t loc = gimple_location (stmt);
 	      inform (loc, "in a call to %qT declared with "
-		      "attribute %<access (%s, %u)%>",
-		      fntype, mode, argno);
+		      "attribute %<%s%>", fntype, access_str);
 	    }
 	}
-      else if (fndecl)
-	{
-	  location_t loc = DECL_SOURCE_LOCATION (fndecl);
-	  inform (loc, "by argument %u of type %qT to %qD declared here",
-		  argno, argtype, fndecl);
-	}
       else
 	{
-	  /* Handle calls through function pointers.  */
-	  location_t loc = gimple_location (stmt);
-	  inform (loc, "by argument %u of type %qT to %qT",
-		  argno, argtype, fntype);
+	  /* For a declaration with no relevant attribute access create
+	     a dummy object and use the formatting function to avoid
+	     having to complicate things here.  */
+	  attr_access ptr_access = { };
+	  if (!access)
+	    access = &ptr_access;
+	  const std::string argtypestr = access->array_as_string (argtype);
+	  if (fndecl)
+	    {
+	      location_t loc (DECL_SOURCE_LOCATION (fndecl));
+	      inform (loc, "by argument %u of type %<%s%> to %qD "
+		      "declared here",
+		      argno, argtypestr.c_str (), fndecl);
+	    }
+	  else
+	    {
+	      /* Handle calls through function pointers.  */
+	      location_t loc (gimple_location (stmt));
+	      inform (loc, "by argument %u of type %<%s%> to %qT",
+		      argno, argtypestr.c_str (), fntype);
+	    }
 	}
 
       if (DECL_P (argbase))

^ permalink raw reply	[flat|nested] 33+ messages in thread

* [PATCH 4/5] - extend -Wstringop-overflow to detect out-of-bounds accesses to array parameters
  2020-07-29  1:13 [PATCH 0/5] add checking of function array parameters (PR 50584) Martin Sebor
                   ` (2 preceding siblings ...)
  2020-07-29  1:20 ` [PATCH 0/5] add checking of function array parameters (PR 50584) Martin Sebor
@ 2020-07-29  1:22 ` Martin Sebor
  2020-08-13 16:31   ` Jeff Law
  2020-07-29  1:24 ` [PATCH 5/5] extend -Warray-bounds " Martin Sebor
  4 siblings, 1 reply; 33+ messages in thread
From: Martin Sebor @ 2020-07-29  1:22 UTC (permalink / raw)
  To: gcc-patches

[-- Attachment #1: Type: text/plain, Size: 250 bytes --]

Patch 4 adds support to the machinery behind -Wstringop-overflow
to issue warnings for (likely) out of bounds accesses in calls to
functions with the internal attribute access specification.  This
implements the feature pr50584 asks for (plus more).

[-- Attachment #2: gcc-50584-4.diff --]
[-- Type: text/x-patch, Size: 52782 bytes --]

[4/5] - Extend -Wstringop-overflow to detect out-of-bounds accesses to array parameters.

gcc/ChangeLog:

	* tree-ssa-uninit.c (maybe_warn_pass_by_reference): Handle attribute
	access internal representation of arrays.

gcc/testsuite/ChangeLog:

	* gcc.dg/uninit-37.c: New test.

gcc/ChangeLog:

	PR c/50584
	* builtins.c (warn_for_access): Add argument.  Distinguish between
	reads and writes.
	(check_access): Add argument.  Distinguish between reads and writes.
	(gimple_call_alloc_size): Set range even on failure.
	(gimple_parm_array_size): New function.
	(compute_objsize): Call it.
	(check_memop_access): Pass check_access an additional argument.
	(expand_builtin_memchr, expand_builtin_strcat): Same.
	(expand_builtin_strcpy, expand_builtin_stpcpy_1): Same.
	(expand_builtin_stpncpy, check_strncat_sizes): Same.
	(expand_builtin_strncat, expand_builtin_strncpy): Same.
	(expand_builtin_memcmp): Same.
	* builtins.h (compute_objsize): Declare a new overload.
	(gimple_parm_array_size): Declare.
	(check_access): Add argument.
	* calls.c (append_attrname): Simplify.
	(maybe_warn_rdwr_sizes): Handle internal attribute access.

gcc/testsuite/ChangeLog:

	PR c/50584
	* c-c++-common/Wsizeof-pointer-memaccess1.c: Disable new expected
	warnings.
	* g++.dg/ext/attr-access.C: Update text of expected warnings.
	* gcc.dg/Wstringop-overflow-23.c: Same.
	* gcc.dg/Wstringop-overflow-24.c: Same.
	* gcc.dg/attr-access-none.c: Same.
	* gcc.dg/Wstringop-overflow-40.c: New test.
	* gcc.dg/attr-access-2.c: New test.

diff --git a/gcc/builtins.c b/gcc/builtins.c
index 228db78f32b..bbaeb3aedd1 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -3312,26 +3312,122 @@ determine_block_size (tree len, rtx len_rtx,
 }
 
 /* For an expression EXP issue an access warning controlled by option OPT
-   with access to a region SLEN bytes in size in the RANGE of sizes.  */
+   with access to a region SIZE bytes in size in the RANGE of sizes.
+   WRITE is true for a write access, READ for a read access, neither for
+   call that may or may not perform an access but for which the range
+   is expected to valid.
+   Returns true when a warning has been issued.  */
 
 static bool
 warn_for_access (location_t loc, tree func, tree exp, int opt, tree range[2],
-		 tree slen, bool access)
+		 tree size, bool write, bool read)
 {
   bool warned = false;
 
-  if (access)
+  if (write && read)
+    {
+      if (tree_int_cst_equal (range[0], range[1]))
+	warned = (func
+		  ? warning_n (loc, opt, tree_to_uhwi (range[0]),
+			       "%K%qD accessing %E byte in a region "
+			       "of size %E",
+			       "%K%qD accessing %E bytes in a region "
+			       "of size %E",
+			       exp, func, range[0], size)
+		  : warning_n (loc, opt, tree_to_uhwi (range[0]),
+			       "%Kaccessing %E byte in a region "
+			       "of size %E",
+			       "%Kaccessing %E bytes in a region "
+			       "of size %E",
+			       exp, range[0], size));
+      else if (tree_int_cst_sign_bit (range[1]))
+	{
+	  /* Avoid printing the upper bound if it's invalid.  */
+	  warned = (func
+		    ? warning_at (loc, opt,
+				  "%K%qD accessing %E or more bytes in "
+				  "a region of size %E",
+				  exp, func, range[0], size)
+		    : warning_at (loc, opt,
+				  "%Kaccessing %E or more bytes in "
+				  "a region of size %E",
+				  exp, range[0], size));
+	}
+      else
+	warned = (func
+		  ? warning_at (loc, opt,
+				"%K%qD accessing between %E and %E bytes "
+				"in a region of size %E",
+				exp, func, range[0], range[1],
+				size)
+		  : warning_at (loc, opt,
+				"%Kaccessing between %E and %E bytes "
+				"in a region of size %E",
+				exp, range[0], range[1],
+				size));
+      return warned;
+    }
+
+  if (write)
+    {
+      if (tree_int_cst_equal (range[0], range[1]))
+	warned = (func
+		  ? warning_n (loc, opt, tree_to_uhwi (range[0]),
+			       "%K%qD writing %E byte into a region "
+			       "of size %E overflows the destination",
+			       "%K%qD writing %E bytes into a region "
+			       "of size %E overflows the destination",
+			       exp, func, range[0], size)
+		  : warning_n (loc, opt, tree_to_uhwi (range[0]),
+			       "%Kwriting %E byte into a region "
+			       "of size %E overflows the destination",
+			       "%Kwriting %E bytes into a region "
+			       "of size %E overflows the destination",
+			       exp, range[0], size));
+      else if (tree_int_cst_sign_bit (range[1]))
+	{
+	  /* Avoid printing the upper bound if it's invalid.  */
+	  warned = (func
+		    ? warning_at (loc, opt,
+				  "%K%qD writing %E or more bytes into "
+				  "a region of size %E overflows "
+				  "the destination",
+				  exp, func, range[0], size)
+		    : warning_at (loc, opt,
+				  "%Kwriting %E or more bytes into "
+				  "a region of size %E overflows "
+				  "the destination",
+				  exp, range[0], size));
+	}
+      else
+	warned = (func
+		  ? warning_at (loc, opt,
+				"%K%qD writing between %E and %E bytes "
+				"into a region of size %E overflows "
+				"the destination",
+				exp, func, range[0], range[1],
+				size)
+		  : warning_at (loc, opt,
+				"%Kwriting between %E and %E bytes "
+				"into a region of size %E overflows "
+				"the destination",
+				exp, range[0], range[1],
+				size));
+      return warned;
+    }
+
+  if (read)
     {
       if (tree_int_cst_equal (range[0], range[1]))
 	warned = (func
 		  ? warning_n (loc, opt, tree_to_uhwi (range[0]),
 			       "%K%qD reading %E byte from a region of size %E",
 			       "%K%qD reading %E bytes from a region of size %E",
-			       exp, func, range[0], slen)
+			       exp, func, range[0], size)
 		  : warning_n (loc, opt, tree_to_uhwi (range[0]),
 			       "%Kreading %E byte from a region of size %E",
 			       "%Kreading %E bytes from a region of size %E",
-			       exp, range[0], slen));
+			       exp, range[0], size));
       else if (tree_int_cst_sign_bit (range[1]))
 	{
 	  /* Avoid printing the upper bound if it's invalid.  */
@@ -3339,59 +3435,47 @@ warn_for_access (location_t loc, tree func, tree exp, int opt, tree range[2],
 		    ? warning_at (loc, opt,
 				  "%K%qD reading %E or more bytes from a region "
 				  "of size %E",
-				  exp, func, range[0], slen)
+				  exp, func, range[0], size)
 		    : warning_at (loc, opt,
 				  "%Kreading %E or more bytes from a region "
 				  "of size %E",
-				  exp, range[0], slen));
+				  exp, range[0], size));
 	}
       else
 	warned = (func
 		  ? warning_at (loc, opt,
 				"%K%qD reading between %E and %E bytes from "
 				"a region of size %E",
-				exp, func, range[0], range[1], slen)
+				exp, func, range[0], range[1], size)
 		  : warning_at (loc, opt,
 				"%Kreading between %E and %E bytes from "
 				"a region of size %E",
-				exp, range[0], range[1], slen));
+				exp, range[0], range[1], size));
 
       return warned;
     }
 
-  if (tree_int_cst_equal (range[0], range[1]))
+  if (tree_int_cst_equal (range[0], range[1])
+      || tree_int_cst_sign_bit (range[1]))
     warned = (func
-	      ? warning_n (loc, opt, tree_to_uhwi (range[0]),
-			   "%K%qD epecting %E byte in a region of size %E",
-			   "%K%qD expecting %E bytes in a region of size %E",
-			   exp, func, range[0], slen)
-	      : warning_n (loc, opt, tree_to_uhwi (range[0]),
-			   "%Kexpecting %E byte in a region of size %E",
-			   "%Kexpecting %E bytes in a region of size %E",
-			   exp, range[0], slen));
-  else if (tree_int_cst_sign_bit (range[1]))
-    {
-      /* Avoid printing the upper bound if it's invalid.  */
-      warned = (func
-		? warning_at (loc, opt,
-			      "%K%qD expecting %E or more bytes in a region "
-			      "of size %E",
-			      exp, func, range[0], slen)
-		: warning_at (loc, opt,
-			      "%Kexpecting %E or more bytes in a region "
-			      "of size %E",
-			      exp, range[0], slen));
-    }
+	      ? warning_at (loc, OPT_Warray_parameter_,
+			    "%K%qD expecting %E or more bytes in a region "
+			    "of size %E",
+			    exp, func, range[0], size)
+	      : warning_at (loc, OPT_Warray_parameter_,
+			    "%Kexpecting %E or more bytes in a region "
+			    "of size %E",
+			    exp, range[0], size));
   else
     warned = (func
-	      ? warning_at (loc, opt,
+	      ? warning_at (loc, OPT_Warray_parameter_,
 			    "%K%qD expecting between %E and %E bytes in "
 			    "a region of size %E",
-			    exp, func, range[0], range[1], slen)
-	      : warning_at (loc, opt,
+			    exp, func, range[0], range[1], size)
+	      : warning_at (loc, OPT_Warray_parameter_,
 			    "%Kexpectting between %E and %E bytes in "
 			    "a region of size %E",
-			    exp, range[0], range[1], slen));
+			    exp, range[0], range[1], size));
   return warned;
 }
 
@@ -3545,8 +3629,9 @@ inform_access (const access_ref &ref, bool write)
    When DSTWRITE is null LEN is checked to verify that it doesn't exceed
    SIZE_MAX.
 
-   ACCESS is true for accesses, false for simple size checks in calls
-   to functions that neither read from nor write to the region.
+   WRITE is true for write accesses, READ is true for reads.  Both are
+   false for simple size checks in calls to functions that neither read
+   from nor write to the region.
 
    When nonnull, PAD points to a more detailed description of the access.
 
@@ -3556,7 +3641,7 @@ inform_access (const access_ref &ref, bool write)
 bool
 check_access (tree exp, tree, tree, tree dstwrite,
 	      tree maxread, tree srcstr, tree dstsize,
-	      bool access /* = true */,
+	      bool write /* = true */, bool read /* = true */,
 	      const access_data *pad /* = NULL */)
 {
   int opt = OPT_Wstringop_overflow_;
@@ -3636,6 +3721,11 @@ check_access (tree exp, tree, tree, tree dstwrite,
     get_size_range (dstwrite, range);
 
   tree func = get_callee_fndecl (exp);
+  /* Read vs write access by built-ins can be determined from the const
+     qualifiers on the pointer argument.  In the absence of attribute
+     access, non-const qualified pointer arguments to user-defined
+     functions are assumed to both read and write the objects.  */
+  const bool builtin = func ? fndecl_built_in_p (func) : false;
 
   /* First check the number of bytes to be written against the maximum
      object size.  */
@@ -3717,49 +3807,10 @@ check_access (tree exp, tree, tree, tree dstwrite,
 				      "the destination",
 				      exp, range[0], dstsize));
 	    }
-	  else if (tree_int_cst_equal (range[0], range[1]))
-	    warned = (func
-		      ? warning_n (loc, opt, tree_to_uhwi (range[0]),
-				   "%K%qD writing %E byte into a region "
-				   "of size %E overflows the destination",
-				   "%K%qD writing %E bytes into a region "
-				   "of size %E overflows the destination",
-				   exp, func, range[0], dstsize)
-		      : warning_n (loc, opt, tree_to_uhwi (range[0]),
-				   "%Kwriting %E byte into a region "
-				   "of size %E overflows the destination",
-				   "%Kwriting %E bytes into a region "
-				   "of size %E overflows the destination",
-				   exp, range[0], dstsize));
-	  else if (tree_int_cst_sign_bit (range[1]))
-	    {
-	      /* Avoid printing the upper bound if it's invalid.  */
-	      warned = (func
-			? warning_at (loc, opt,
-				      "%K%qD writing %E or more bytes into "
-				      "a region of size %E overflows "
-				      "the destination",
-				      exp, func, range[0], dstsize)
-			: warning_at (loc, opt,
-				      "%Kwriting %E or more bytes into "
-				      "a region of size %E overflows "
-				      "the destination",
-				      exp, range[0], dstsize));
-	    }
 	  else
-	    warned = (func
-		      ? warning_at (loc, opt,
-				    "%K%qD writing between %E and %E bytes "
-				    "into a region of size %E overflows "
-				    "the destination",
-				    exp, func, range[0], range[1],
-				    dstsize)
-		      : warning_at (loc, opt,
-				    "%Kwriting between %E and %E bytes "
-				    "into a region of size %E overflows "
-				    "the destination",
-				    exp, range[0], range[1],
-				    dstsize));
+	    warned = warn_for_access (loc, func, exp, opt, range, dstsize,
+				      write, read && !builtin);
+
 	  if (warned)
 	    {
 	      TREE_NO_WARNING (exp) = true;
@@ -3872,7 +3923,8 @@ check_access (tree exp, tree, tree, tree dstwrite,
       location_t loc = tree_nonartificial_location (exp);
       loc = expansion_point_location_if_in_system_header (loc);
 
-      if (warn_for_access (loc, func, exp, opt, range, slen, access))
+      if (warn_for_access (loc, func, exp, opt, range, slen,
+			   write && !builtin, read))
 	{
 	  TREE_NO_WARNING (exp) = true;
 	  if (pad)
@@ -3885,8 +3937,11 @@ check_access (tree exp, tree, tree, tree dstwrite,
 }
 
 /* If STMT is a call to an allocation function, returns the constant
-   size of the object allocated by the call represented as sizetype.
-   If nonnull, sets RNG1[] to the range of the size.  */
+   maximum size of the object allocated by the call represented as
+   sizetype.  If nonnull, sets RNG1[] to the range of the size.
+   When nonnull, uses RVALS for range information, otherwise calls
+   get_range_info to get it.
+   Returns null when STMT is not a call to a valid allocation function.  */
 
 tree
 gimple_call_alloc_size (gimple *stmt, wide_int rng1[2] /* = NULL */,
@@ -3942,8 +3997,14 @@ gimple_call_alloc_size (gimple *stmt, wide_int rng1[2] /* = NULL */,
   if (!rng1)
     rng1 = rng1_buf;
 
+  const int prec = ADDR_MAX_PRECISION;
+  const tree size_max = TYPE_MAX_VALUE (sizetype);
   if (!get_range (size, rng1, rvals))
-    return NULL_TREE;
+    {
+      /* Use the full non-negative range on failure.  */
+      rng1[0] = wi::zero (prec);
+      rng1[1] = wi::to_wide (size_max, prec);
+    }
 
   if (argidx2 > nargs && TREE_CODE (size) == INTEGER_CST)
     return fold_convert (sizetype, size);
@@ -3953,10 +4014,13 @@ gimple_call_alloc_size (gimple *stmt, wide_int rng1[2] /* = NULL */,
   tree n = argidx2 < nargs ? gimple_call_arg (stmt, argidx2) : integer_one_node;
   wide_int rng2[2];
   if (!get_range (n, rng2, rvals))
-    return NULL_TREE;
+    {
+      /* As above, use the full non-negative range on failure.  */
+      rng2[0] = wi::zero (prec);
+      rng2[1] = wi::to_wide (size_max, prec);
+    }
 
   /* Extend to the maximum precision to avoid overflow.  */
-  const int prec = ADDR_MAX_PRECISION;
   rng1[0] = wide_int::from (rng1[0], prec, UNSIGNED);
   rng1[1] = wide_int::from (rng1[1], prec, UNSIGNED);
   rng2[0] = wide_int::from (rng2[0], prec, UNSIGNED);
@@ -3966,7 +4030,6 @@ gimple_call_alloc_size (gimple *stmt, wide_int rng1[2] /* = NULL */,
      of SIZE_MAX and the product of the upper bounds as a constant.  */
   rng1[0] = rng1[0] * rng2[0];
   rng1[1] = rng1[1] * rng2[1];
-  tree size_max = TYPE_MAX_VALUE (sizetype);
   if (wi::gtu_p (rng1[1], wi::to_wide (size_max, prec)))
     {
       rng1[1] = wi::to_wide (size_max);
@@ -3976,6 +4039,61 @@ gimple_call_alloc_size (gimple *stmt, wide_int rng1[2] /* = NULL */,
   return wide_int_to_tree (sizetype, rng1[1]);
 }
 
+/* For an access to an object referenced to by the function parameter PTR
+   of pointer type, and set RNG[] to the range of sizes of the object
+   obtainedfrom the attribute access specification for the current function.
+   Return the function parameter on success and null otherwise.  */
+
+tree
+gimple_parm_array_size (tree ptr, wide_int rng[2],
+			const vr_values * /* = NULL */)
+{
+  /* For a function argument try to determine the byte size of the array
+     from the current function declaratation (e.g., attribute access or
+     related).  */
+  tree var = SSA_NAME_VAR (ptr);
+  if (TREE_CODE (var) != PARM_DECL)
+    return NULL_TREE;
+
+  const unsigned prec = TYPE_PRECISION (sizetype);
+
+  rdwr_map rdwr_idx;
+  attr_access *access = get_parm_access (rdwr_idx, var);
+  if (!access)
+    return NULL_TREE;
+
+  if (access->sizarg != UINT_MAX)
+    {
+      /* TODO: Try to extract the range from the argument based on
+	 those of subsequent assertions or based on known calls to
+	 the current function.  */
+      return NULL_TREE;
+    }
+
+  if (!access->minsize)
+    return NULL_TREE;
+
+  /* Only consider ordinary array bound at level 2 (or above if it's
+     ever added).  */
+  if (warn_array_parameter < 2 && !access->static_p)
+    return NULL_TREE;
+
+  rng[0] = wi::zero (prec);
+  rng[1] = wi::uhwi (access->minsize, prec);
+  /* If the PTR argument points to an array multiply MINSIZE by the size
+     of array element type.  Otherwise, multiply it by the size of what
+     the pointer points to.  */
+  tree eltype = TREE_TYPE (TREE_TYPE (ptr));
+  if (TREE_CODE (eltype) == ARRAY_TYPE)
+    eltype = TREE_TYPE (eltype);
+  tree size = TYPE_SIZE_UNIT (eltype);
+  if (!size || TREE_CODE (size) != INTEGER_CST)
+    return NULL_TREE;
+
+  rng[1] *= wi::to_wide (size, prec);
+  return var;
+}
+
 /* Wrapper around the wide_int overload of get_range.  Returns the same
    result but accepts offset_int instead.  */
 
@@ -4168,6 +4286,21 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
 	  return false;
 	}
 
+      if (gimple_nop_p (stmt))
+	{
+	  /* For a function argument try to determine the byte size
+	     of the array from the current function declaratation
+	     (e.g., attribute access or related).  */
+	  wide_int wr[2];
+	  tree ref = gimple_parm_array_size (ptr, wr, rvals);
+	  if (!ref)
+	    return NULL_TREE;
+	  pref->ref = ref;
+	  pref->sizrng[0] = offset_int::from (wr[0], UNSIGNED);
+	  pref->sizrng[1] = offset_int::from (wr[1], UNSIGNED);
+	  return true;
+	}
+
       /* TODO: Handle PHI.  */
 
       if (!is_gimple_assign (stmt))
@@ -4220,9 +4353,9 @@ compute_objsize (tree ptr, int ostype, access_ref *pref,
 
 /* Convenience wrapper around the above.  */
 
-static tree
+tree
 compute_objsize (tree ptr, int ostype, access_ref *pref,
-		 const vr_values *rvals = NULL)
+		 const vr_values *rvals /* = NULL */)
 {
   bitmap visited = NULL;
 
@@ -4291,7 +4424,7 @@ check_memop_access (tree exp, tree dest, tree src, tree size)
   tree dstsize = compute_objsize (dest, 0, &data.dst);
 
   return check_access (exp, dest, src, size, /*maxread=*/NULL_TREE,
-		       srcsize, dstsize, true, &data);
+		       srcsize, dstsize, true, true, &data);
 }
 
 /* Validate memchr arguments without performing any expansion.
@@ -4315,7 +4448,7 @@ expand_builtin_memchr (tree exp, rtx)
       tree size = compute_objsize (arg1, 0, &data.src);
       check_access (exp, /*dst=*/NULL_TREE, /*src=*/NULL_TREE, len,
 		    /*maxread=*/NULL_TREE, size, /*objsize=*/NULL_TREE,
-		    true, &data);
+		    false, true, &data);
     }
 
   return NULL_RTX;
@@ -4599,7 +4732,7 @@ expand_builtin_strcat (tree exp)
   tree destsize = compute_objsize (dest, warn_stringop_overflow - 1, &data.dst);
 
   check_access (exp, dest, src, /*size=*/NULL_TREE, /*maxread=*/NULL_TREE, src,
-		destsize, true, &data);
+		destsize, true, true, &data);
 
   return NULL_RTX;
 }
@@ -4624,7 +4757,7 @@ expand_builtin_strcpy (tree exp, rtx target)
       tree destsize = compute_objsize (dest, warn_stringop_overflow - 1,
 				       &data.dst);
       check_access (exp, dest, src, /*size=*/NULL_TREE, /*maxread=*/NULL_TREE,
-		    src, destsize, true, &data);
+		    src, destsize, true, true, &data);
     }
 
   if (rtx ret = expand_builtin_strcpy_args (exp, dest, src, target))
@@ -4684,7 +4817,7 @@ expand_builtin_stpcpy_1 (tree exp, rtx target, machine_mode mode)
       tree destsize = compute_objsize (dst, warn_stringop_overflow - 1,
 				       &data.dst);
       check_access (exp, dst, src, /*size=*/NULL_TREE, /*maxread=*/NULL_TREE,
-		    src, destsize, true, &data);
+		    src, destsize, true, true, &data);
     }
 
   /* If return value is ignored, transform stpcpy into strcpy.  */
@@ -4798,7 +4931,7 @@ expand_builtin_stpncpy (tree exp, rtx)
   tree destsize = compute_objsize (dest, warn_stringop_overflow - 1, &data.dst);
 
   check_access (exp, dest, src, len, /*maxread=*/NULL_TREE, src, destsize,
-		true, &data);
+		true, true, &data);
 
   return NULL_RTX;
 }
@@ -4878,7 +5011,7 @@ check_strncat_sizes (tree exp, tree objsize)
   /* The number of bytes to write is LEN but check_access will alsoa
      check SRCLEN if LEN's value isn't known.  */
   return check_access (exp, dest, src, /*size=*/NULL_TREE, maxread, srclen,
-		       objsize, true, &data);
+		       objsize, true, true, &data);
 }
 
 /* Similar to expand_builtin_strcat, do some very basic size validation
@@ -4951,7 +5084,7 @@ expand_builtin_strncat (tree exp, rtx)
     srclen = maxread;
 
   check_access (exp, dest, src, NULL_TREE, maxread, srclen, destsize,
-		true, &data);
+		true, true, &data);
 
   return NULL_RTX;
 }
@@ -4987,7 +5120,7 @@ expand_builtin_strncpy (tree exp, rtx target)
       /* The number of bytes to write is LEN but check_access will also
 	 check SLEN if LEN's value isn't known.  */
       check_access (exp, dest, src, len, /*maxread=*/NULL_TREE, src,
-		    destsize, true, &data);
+		    destsize, true, true, &data);
     }
 
   /* We must be passed a constant len and src parameter.  */
@@ -5304,14 +5437,14 @@ expand_builtin_memcmp (tree exp, rtx target, bool result_eq)
   tree size = compute_objsize (arg1, 0, &data.src);
   no_overflow = check_access (exp, /*dst=*/NULL_TREE, /*src=*/NULL_TREE,
 			      len, /*maxread=*/NULL_TREE, size,
-			      /*objsize=*/NULL_TREE, true, &data);
+			      /*objsize=*/NULL_TREE, false, true, &data);
   if (no_overflow)
     {
       access_data data;
       size = compute_objsize (arg2, 0, &data.src);
       no_overflow = check_access (exp, /*dst=*/NULL_TREE, /*src=*/NULL_TREE,
 				  len,  /*maxread=*/NULL_TREE, size,
-				  /*objsize=*/NULL_TREE, true, &data);
+				  /*objsize=*/NULL_TREE, false, true, &data);
     }
 
   /* If the specified length exceeds the size of either object, 
diff --git a/gcc/builtins.h b/gcc/builtins.h
index 8b812ceb2c4..c909ae11845 100644
--- a/gcc/builtins.h
+++ b/gcc/builtins.h
@@ -133,13 +133,6 @@ extern tree fold_call_stmt (gcall *, bool);
 extern void set_builtin_user_assembler_name (tree decl, const char *asmspec);
 extern bool is_simple_builtin (tree);
 extern bool is_inexpensive_builtin (tree);
-
-class vr_values;
-tree gimple_call_alloc_size (gimple *, wide_int[2] = NULL,
-			     const vr_values * = NULL);
-extern tree compute_objsize (tree, int, tree * = NULL, tree * = NULL,
-			     const vr_values * = NULL);
-
 extern bool readonly_data_expr (tree exp);
 extern bool init_target_chars (void);
 extern unsigned HOST_WIDE_INT target_newline;
@@ -184,7 +177,15 @@ struct access_data
   access_ref dst, src;
 };
 
+class vr_values;
+extern tree gimple_call_alloc_size (gimple *, wide_int[2] = NULL,
+				    const vr_values * = NULL);
+extern tree gimple_parm_array_size (tree, wide_int[2], const vr_values * = NULL);
+extern tree compute_objsize (tree, int, tree * = NULL, tree * = NULL,
+			     const vr_values * = NULL);
+extern tree compute_objsize (tree, int, access_ref *, const vr_values * = NULL);
+
 extern bool check_access (tree, tree, tree, tree, tree, tree, tree,
-			  bool = true, const access_data * = NULL);
+			  bool = true, bool = true, const access_data * = NULL);
 
 #endif /* GCC_BUILTINS_H */
diff --git a/gcc/calls.c b/gcc/calls.c
index 622417ff74b..ecb966d6cb6 100644
--- a/gcc/calls.c
+++ b/gcc/calls.c
@@ -58,6 +58,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "builtins.h"
 #include "gimple-fold.h"
 
+#include "tree-pretty-print.h"
+
 /* Like PREFERRED_STACK_BOUNDARY but in units of bytes, not bits.  */
 #define STACK_BYTES (PREFERRED_STACK_BOUNDARY / BITS_PER_UNIT)
 
@@ -1883,36 +1885,20 @@ fntype_argno_type (tree fntype, unsigned argno)
   return NULL_TREE;
 }
 
-/* Helper to append the "rdwr" attribute specification described
-   by ACCESS to the array ATTRSTR with size STRSIZE.  Used in
+/* Helper to append the "human readable" attribute access specification
+   described by ACCESS to the array ATTRSTR with size STRSIZE.  Used in
    diagnostics.  */
 
 static inline void
 append_attrname (const std::pair<int, attr_access> &access,
 		 char *attrstr, size_t strsize)
 {
-  /* Append the relevant attribute to the string.  This (deliberately)
-     appends the attribute pointer operand even when none was specified.  */
-  size_t len = strlen (attrstr);
-
-  const char* const atname
-    = (access.second.mode == attr_access::read_only
-       ? "read_only"
-       : (access.second.mode == attr_access::write_only
-	  ? "write_only"
-	  : (access.second.mode == attr_access::read_write
-	     ? "read_write" : "none")));
-
-  const char *sep = len ? ", " : "";
-
-  if (access.second.sizarg == UINT_MAX)
-    snprintf (attrstr + len, strsize - len,
-	      "%s%s (%i)", sep, atname,
-	      access.second.ptrarg + 1);
-  else
-    snprintf (attrstr + len, strsize - len,
-	      "%s%s (%i, %i)", sep, atname,
-	      access.second.ptrarg + 1, access.second.sizarg + 1);
+  if (access.second.internal_p)
+    return;
+
+  tree str = access.second.to_external_string ();
+  gcc_assert (strsize >= (size_t) TREE_STRING_LENGTH (str));
+  strcpy (attrstr, TREE_STRING_POINTER (str));
 }
 
 /* Iterate over attribute access read-only, read-write, and write-only
@@ -1939,6 +1925,7 @@ maybe_warn_rdwr_sizes (rdwr_map *rwm, tree exp)
     return;
 
   auto_diagnostic_group adg;
+  bool warned = false;
 
   /* A string describing the attributes that the warnings issued by this
      function apply to.  Used to print one informational note per function
@@ -1967,35 +1954,40 @@ maybe_warn_rdwr_sizes (rdwr_map *rwm, tree exp)
       if (!access.second.ptr)
 	continue;
 
-      tree argtype = fntype_argno_type (fntype, ptridx);
-      argtype = TREE_TYPE (argtype);
+      tree ptrtype = fntype_argno_type (fntype, ptridx);
+      tree argtype = TREE_TYPE (ptrtype);
 
-      tree size;
+      /* The size of the access by the call.  */
+      tree access_size;
       if (sizidx == -1)
 	{
-	  /* If only the pointer attribute operand was specified
-	     and not size, set SIZE to the size of one element of
-	     the pointed to type to detect smaller objects (null
-	     pointers are diagnosed in this case only if
-	     the pointer is also declared with attribute nonnull.  */
-	  size = size_one_node;
+	  /* If only the pointer attribute operand was specified and
+	     not size, set SIZE to the greater of MINSIZE or size of
+	     one element of the pointed to type to detect smaller
+	     objects (null pointers are diagnosed in this case only
+	     if the pointer is also declared with attribute nonnull.  */
+	  if (access.second.minsize
+	      && access.second.minsize != HOST_WIDE_INT_M1U)
+	    access_size = build_int_cstu (sizetype, access.second.minsize);
+	  else
+	    access_size = size_one_node;
 	}
       else
-	size = rwm->get (sizidx)->size;
+	access_size = rwm->get (sizidx)->size;
 
+      bool warned = false;
+      location_t loc = EXPR_LOCATION (exp);
       tree ptr = access.second.ptr;
       tree sizrng[2] = { size_zero_node, build_all_ones_cst (sizetype) };
-      if (get_size_range (size, sizrng, true)
+      if (get_size_range (access_size, sizrng, true)
 	  && tree_int_cst_sgn (sizrng[0]) < 0
 	  && tree_int_cst_sgn (sizrng[1]) < 0)
 	{
 	  /* Warn about negative sizes.  */
-	  bool warned = false;
-	  location_t loc = EXPR_LOCATION (exp);
 	  if (tree_int_cst_equal (sizrng[0], sizrng[1]))
 	    warned = warning_at (loc, OPT_Wstringop_overflow_,
 				 "%Kargument %i value %E is negative",
-				 exp, sizidx + 1, size);
+				 exp, sizidx + 1, access_size);
 	  else
 	    warned = warning_at (loc, OPT_Wstringop_overflow_,
 				 "%Kargument %i range [%E, %E] is negative",
@@ -2012,44 +2004,56 @@ maybe_warn_rdwr_sizes (rdwr_map *rwm, tree exp)
 	{
 	  if (COMPLETE_TYPE_P (argtype))
 	    {
-	      /* Multiple SIZE by the size of the type the pointer
-		 argument points to.  If it's incomplete the size
-		 is used as is.  */
-	      size = NULL_TREE;
+	      /* Multiply ACCESS_SIZE by the size of the type the pointer
+		 argument points to.  If it's incomplete the size is used
+		 as is.  */
+	      access_size = NULL_TREE;
 	      if (tree argsize = TYPE_SIZE_UNIT (argtype))
 		if (TREE_CODE (argsize) == INTEGER_CST)
 		  {
 		    const int prec = TYPE_PRECISION (sizetype);
 		    wide_int minsize = wi::to_wide (sizrng[0], prec);
 		    minsize *= wi::to_wide (argsize, prec);
-		    size = wide_int_to_tree (sizetype, minsize);
+		    access_size = wide_int_to_tree (sizetype, minsize);
 		  }
 	    }
 	}
       else
-	size = NULL_TREE;
+	access_size = NULL_TREE;
 
-      if (sizidx >= 0
-	  && integer_zerop (ptr)
-	  && tree_int_cst_sgn (sizrng[0]) > 0)
+      if (integer_zerop (ptr))
 	{
-	  /* Warn about null pointers with positive sizes.  This is
-	     different from also declaring the pointer argument with
-	     attribute nonnull when the function accepts null pointers
-	     only when the corresponding size is zero.  */
-	  bool warned = false;
-	  const location_t loc = EXPR_LOC_OR_LOC (ptr, EXPR_LOCATION (exp));
-	  if (tree_int_cst_equal (sizrng[0], sizrng[1]))
-	    warned = warning_at (loc, OPT_Wnonnull,
-				 "%Kargument %i is null but the corresponding "
-				 "size argument %i value is %E",
-				 exp, ptridx + 1, sizidx + 1, size);
-	  else
-	    warned = warning_at (loc, OPT_Wnonnull,
-				 "%Kargument %i is null but the corresponding "
-				 "size argument %i range is [%E, %E]",
-				 exp, ptridx + 1, sizidx + 1,
-				 sizrng[0], sizrng[1]);
+	  if (sizidx >= 0 && tree_int_cst_sgn (sizrng[0]) > 0)
+	    {
+	      /* Warn about null pointers with positive sizes.  This is
+		 different from also declaring the pointer argument with
+		 attribute nonnull when the function accepts null pointers
+		 only when the corresponding size is zero.  */
+	      if (tree_int_cst_equal (sizrng[0], sizrng[1]))
+		warned = warning_at (loc, OPT_Wnonnull,
+				     "%Kargument %i is null but "
+				     "the corresponding size argument %i "
+				     "value is %E",
+				     exp, ptridx + 1, sizidx + 1, access_size);
+	      else
+		warned = warning_at (loc, OPT_Wnonnull,
+				     "%Kargument %i is null but "
+				     "the corresponding size argument %i "
+				     "range is [%E, %E]",
+				     exp, ptridx + 1, sizidx + 1,
+				     sizrng[0], sizrng[1]);
+	    }
+	  else if (access_size && access.second.static_p)
+	    {
+	      /* Warn about null pointers for [static N] array arguments
+		 but do not warn for ordinary (i.e., nonstatic) arrays.  */
+	      warned = warning_at (loc, OPT_Wnonnull,
+				   "%Kargument %i to %<%T[static %E]%> null "
+				   "where non-null expected",
+				   exp, ptridx + 1, argtype,
+				   sizrng[0]);
+	    }
+
 	  if (warned)
 	    {
 	      append_attrname (access, attrstr, sizeof attrstr);
@@ -2058,54 +2062,76 @@ maybe_warn_rdwr_sizes (rdwr_map *rwm, tree exp)
 	    }
 	}
 
-      tree objsize = compute_objsize (ptr, 0);
+      access_data data;
+      access_ref* const pobj = (access.second.mode == attr_access::write_only
+				? &data.dst : &data.src);
+      tree objsize = compute_objsize (ptr, 1, pobj);
 
-      tree srcsize;
-      if (access.second.mode == attr_access::write_only)
-	{
-	  /* For a write-only argument there is no source.  */
-	  srcsize = NULL_TREE;
-	}
-      else
+      /* The size of the destination or source object.  */
+      tree dstsize = NULL_TREE, srcsize = NULL_TREE;
+      if (access.second.mode == attr_access::read_only
+	  || access.second.mode == attr_access::none)
 	{
-	  /* For read-only and read-write attributes also set the source
-	     size.  */
+	  /* For a read-only argument there is no destination.  For
+	     no access, set the source as well and differentiate via
+	     the access flag below.  */
 	  srcsize = objsize;
-	  if (access.second.mode == attr_access::read_only
-	      || access.second.mode == attr_access::none)
-	    {
-	      /* For a read-only attribute there is no destination so
-		 clear OBJSIZE.  This emits "reading N bytes" kind of
-		 diagnostics instead of the "writing N bytes" kind,
-		 unless MODE is none.  */
-	      objsize = NULL_TREE;
-	    }
 	}
+      else
+	dstsize = objsize;
 
-      /* Clear the no-warning bit in case it was set in a prior
-	 iteration so that accesses via different arguments are
-	 diagnosed.  */
+      /* Clear the no-warning bit in case it was set by check_access
+	 in a prior iteration so that accesses via different arguments
+	 are diagnosed.  */
       TREE_NO_WARNING (exp) = false;
-      check_access (exp, NULL_TREE, NULL_TREE, size, /*maxread=*/ NULL_TREE,
-		    srcsize, objsize, access.second.mode != attr_access::none);
+      /* Set for accesses by functions explicitly declared with attribute
+	 access other than none, and clear for functions internally decorated
+	 with the attribute for user-defined functions with mode of none.  */
+      const bool read_access
+	= (access.second.mode != attr_access::none
+	   && access.second.mode != attr_access::write_only);
+      const bool write_access
+	= (access.second.mode != attr_access::none
+	   && access.second.mode != attr_access::read_only
+	   && !TYPE_READONLY (argtype));
+      check_access (exp, /*dst=*/ NULL_TREE, /*src=*/ NULL_TREE, access_size,
+		    /*maxread=*/ NULL_TREE, srcsize, dstsize, write_access,
+		    read_access, &data);
 
       if (TREE_NO_WARNING (exp))
-	/* If check_access issued a warning above, append the relevant
-	   attribute to the string.  */
-	append_attrname (access, attrstr, sizeof attrstr);
-    }
+	{
+	  warned = true;
 
-  if (!*attrstr)
-    return;
+	  if (access.second.internal_p)
+	    inform (loc, "referencing argument %u of type %qT",
+		    ptridx + 1, ptrtype);
+	  else
+	    /* If check_access issued a warning above, append the relevant
+	       attribute to the string.  */
+	    append_attrname (access, attrstr, sizeof attrstr);
+	}
+    }
 
-  if (fndecl)
-    inform (DECL_SOURCE_LOCATION (fndecl),
-	    "in a call to function %qD declared with attribute %qs",
-	    fndecl, attrstr);
-  else
-    inform (EXPR_LOCATION (fndecl),
-	    "in a call with type %qT and attribute %qs",
-	    fntype, attrstr);
+  if (*attrstr)
+    {
+      if (fndecl)
+	inform (DECL_SOURCE_LOCATION (fndecl),
+		"in a call to function %qD declared with attribute %qs",
+		fndecl, attrstr);
+      else
+	inform (EXPR_LOCATION (fndecl),
+		"in a call with type %qT and attribute %qs",
+		fntype, attrstr);
+    }
+  else if (warned)
+    {
+      if (fndecl)
+	inform (DECL_SOURCE_LOCATION (fndecl),
+		"in a call to function %qD", fndecl);
+      else
+	inform (EXPR_LOCATION (fndecl),
+		"in a call with type %qT", fntype);
+    }
 
   /* Set the bit in case if was cleared and not set above.  */
   TREE_NO_WARNING (exp) = true;
diff --git a/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess1.c b/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess1.c
index c4127b805ab..38e06ba5e62 100644
--- a/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess1.c
+++ b/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess1.c
@@ -1,7 +1,7 @@
 /* Test -Wsizeof-pointer-memaccess warnings.  */
 /* { dg-do compile } */
-/* { dg-options "-Wall -Wno-array-bounds -Wno-sizeof-array-argument" } */
-/* { dg-options "-Wall -Wno-array-bounds -Wno-sizeof-array-argument -Wno-c++-compat" { target c } } */
+/* { dg-options "-Wall -Wno-array-bounds -Wno-sizeof-array-argument -Wno-stringop-overflow" } */
+/* { dg-options "-Wall -Wno-array-bounds -Wno-sizeof-array-argument -Wno-c++-compat -Wno-stringop-overflow" { target c } } */
 /* { dg-require-effective-target alloca } */
 
 typedef __SIZE_TYPE__ size_t;
diff --git a/gcc/testsuite/g++.dg/ext/attr-access.C b/gcc/testsuite/g++.dg/ext/attr-access.C
index 3b9c1a36e30..b7b2a5fc4bf 100644
--- a/gcc/testsuite/g++.dg/ext/attr-access.C
+++ b/gcc/testsuite/g++.dg/ext/attr-access.C
@@ -42,8 +42,8 @@ void call_rdwrp1_rdwrr2_O0 (void)
   int32_t x[1] = { };
 
   rdwrp1_rdwrr2 (x, x[0]);
-  rdwrp1_rdwrr2 (x, x[1]);        // { dg-warning "writing 4 bytes into a region of size 0" }
-  rdwrp1_rdwrr2 (x + 1, x[0]);    // { dg-warning "writing 4 bytes into a region of size 0" }
+  rdwrp1_rdwrr2 (x, x[1]);        // { dg-warning "accessing 4 bytes in a region of size 0" }
+  rdwrp1_rdwrr2 (x + 1, x[0]);    // { dg-warning "accessing 4 bytes in a region of size 0" }
 }
 
 void call_wop1_wor2_O0 (void)
@@ -84,12 +84,12 @@ void call_rdwrp1_rdwrr2_O1 (void)
   int32_t &r2 = *(int32_t*)((char*)p1 + 1);
 
   rdwrp1_rdwrr2 (x, x[0]);
-  rdwrp1_rdwrr2 (x, x[1]);        // { dg-warning "writing 4 bytes into a region of size 0" }
-  rdwrp1_rdwrr2 (x + 1, x[0]);    // { dg-warning "writing 4 bytes into a region of size 0" }
+  rdwrp1_rdwrr2 (x, x[1]);        // { dg-warning "accessing 4 bytes in a region of size 0" }
+  rdwrp1_rdwrr2 (x + 1, x[0]);    // { dg-warning "accessing 4 bytes in a region of size 0" }
 
   rdwrp1_rdwrr2 (p0, r0);
-  rdwrp1_rdwrr2 (p0, r2);         // { dg-warning "writing 4 bytes into a region of size 2" }
-  rdwrp1_rdwrr2 (p1, r0);         // { dg-warning "writing 4 bytes into a region of size 3" }
+  rdwrp1_rdwrr2 (p0, r2);         // { dg-warning "accessing 4 bytes in a region of size 2" }
+  rdwrp1_rdwrr2 (p1, r0);         // { dg-warning "accessing 4 bytes in a region of size 3" }
 }
 
 void call_wop1_wor2_O1 (void)
diff --git a/gcc/testsuite/gcc.dg/Warray-bounds-30.c b/gcc/testsuite/gcc.dg/Warray-bounds-30.c
index b9965682101..048a95d6dcf 100644
--- a/gcc/testsuite/gcc.dg/Warray-bounds-30.c
+++ b/gcc/testsuite/gcc.dg/Warray-bounds-30.c
@@ -73,8 +73,7 @@ void test_global_int_array (void)
 
   T (&p[min]);      /* { dg-warning "subscript -\[0-9\]+ is \(below|outside\) array bounds of .int\\\[1]." } */
   T (&p[-1]);       /* { dg-warning "subscript -1 is \(below|outside\) array bounds of .int\\\[1]." } */
-  T (&p[0]);
-  T (&p[1]);
+  T (&p[0], &p[1]);
   T (&p[2]);        /* { dg-warning "subscript 2 is \(above|outside\) array bounds of .int\\\[1]." } */
   T (&p[max]);      /* { dg-warning "subscript \[0-9\]+ is \(above|outside\) array bounds of .int\\\[1]." } */
 
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-23.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-23.c
index 6d9fcb9d52e..755ebea5ff4 100644
--- a/gcc/testsuite/gcc.dg/Wstringop-overflow-23.c
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-23.c
@@ -20,7 +20,7 @@ typedef __INT32_TYPE__ int32_t;
 /* Exercise null pointer detection.  */
 
 RDONLY (2, 1) void
-rd2_1 (int, const void*);       // { dg-message "in a call to function 'rd2_1' declared with attribute 'read_only \\\(2, 1\\\)" }
+rd2_1 (int, const void*);       // { dg-message "in a call to function 'rd2_1' declared with attribute 'access \\\(read_only, 2, 1\\\)" "note" }
 
 void test_rd2_1 (void)
 {
@@ -44,7 +44,7 @@ void test_rd2_1 (void)
 }
 
 WRONLY (3, 1) void
-wr3_1 (int, int, void*);        // { dg-message "in a call to function 'wr3_1' declared with attribute 'write_only \\\(3, 1\\\)" }
+wr3_1 (int, int, void*);        // { dg-message "in a call to function 'wr3_1' declared with attribute 'access \\\(write_only, 3, 1\\\)" }
 
 void test_wr3_1 (void)
 {
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-24.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-24.c
index a21a1f17a2b..049d1c6981c 100644
--- a/gcc/testsuite/gcc.dg/Wstringop-overflow-24.c
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-24.c
@@ -23,7 +23,7 @@ extern char d1[1], d2[2], d3[3];
    the attribute without a size operand.  */
 
 RDONLY (1) void
-rd1_int (const int32_t*);   // { dg-message "in a call to function 'rd1_int' declared with attribute 'read_only \\\(1\\\)'" }
+rd1_int (const int32_t*);   // { dg-message "in a call to function 'rd1_int' declared with attribute 'access \\\(read_only, 1\\\)'" "note" }
 
 void test_rd1_int (void)
 {
@@ -39,7 +39,7 @@ void test_rd1_int (void)
    the attribute and with non-zero size.  */
 
 RDONLY (2, 1) void
-rd2_1 (int, const void*);   // { dg-message "in a call to function 'rd2_1' declared with attribute 'read_only \\\(2, 1\\\)" }
+rd2_1 (int, const void*);   // { dg-message "in a call to function 'rd2_1' declared with attribute 'access \\\(read_only, 2, 1\\\)" "note" }
 
 void test_rd2_1 (void)
 {
@@ -49,7 +49,7 @@ void test_rd2_1 (void)
 }
 
 WRONLY (3, 1) void
-wr3_1 (int, int, void*);    // { dg-message "in a call to function 'wr3_1' declared with attribute 'write_only \\\(3, 1\\\)" }
+wr3_1 (int, int, void*);    // { dg-message "in a call to function 'wr3_1' declared with attribute 'access \\\(write_only, 3, 1\\\)" "note" }
 
 void test_wr3_1 (void)
 {
@@ -157,7 +157,7 @@ void test_rd6_1_wr5_2_rd4_3 (void)
 {
   rd6_1_wr5_2_rd4_3 (7, 2, 1, d1, d2, s3);   // { dg-warning "reading 7 bytes from a region of size 3" }
   rd6_1_wr5_2_rd4_3 (3, 8, 1, d1, d2, s3);   // { dg-warning "writing 8 bytes into a region of size 2" }
-  rd6_1_wr5_2_rd4_3 (3, 2, 9, d1, d2, s3);   // { dg-warning "writing 9 bytes into a region of size 1" }
+  rd6_1_wr5_2_rd4_3 (3, 2, 9, d1, d2, s3);   // { dg-warning "accessing 9 bytes in a region of size 1" }
 }
 
 
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-40.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-40.c
new file mode 100644
index 00000000000..386c92dc7a8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-40.c
@@ -0,0 +1,120 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+   { dg-do compile }
+   { dg-options "-Wall" } */
+
+typedef __INT16_TYPE__ int16_t;
+
+void fa2 (int16_t[2]);
+void fxa2 (int16_t[2]) __attribute__ ((nonnull));
+
+void fas2 (int16_t[static 2]);
+
+void fvla (unsigned n, int16_t[n]);
+
+void test_array_1_dim (void)
+{
+  int16_t a1[1];
+  int16_t a2[2];
+  int16_t i;
+
+  fa2 (0);
+  fa2 (a2);
+  fa2 (a1);                   // { dg-warning "'fa2' accessing 4 bytes in a region of size 2 " }
+  fa2 (&i);                   // { dg-warning "'fa2' accessing 4 bytes in a region of size 2 " }
+
+  fxa2 (0);                   // { dg-warning "\\\[-Wnonnull" }
+  fxa2 (a2);
+  fxa2 (a1);                  // { dg-warning "'fxa2' accessing 4 bytes in a region of size 2 " }
+  fxa2 (&i);                  // { dg-warning "'fxa2' accessing 4 bytes in a region of size 2 " }
+
+  fas2 (0);                   // { dg-warning "\\\[-Wnonnull" }
+  fas2 (a2);
+  fas2 (a1);                  // { dg-warning "'fas2' accessing 4 bytes in a region of size 2 " }
+  fas2 (&i);                  // { dg-warning "'fas2' accessing 4 bytes in a region of size 2 " }
+
+  fvla (1, 0);                // { dg-warning "\\\[-Wnonnull" }
+  fvla (1, &i);
+  fvla (2, a2);
+  fvla (2, a1);               // { dg-warning "'fvla' accessing 4 bytes in a region of size 2 " }
+  fvla (2, &i);               // { dg-warning "'fvla' accessing 4 bytes in a region of size 2 " }
+}
+
+
+void fac2 (const int16_t[2]);
+void fxac2 (const int16_t[2]) __attribute__ ((nonnull));
+
+void facs2 (const int16_t[static 2]);
+
+void fvlac (unsigned n, const int16_t[n]);
+
+void test_const_array_1_dim (void)
+{
+  int16_t a1[1];
+  int16_t a2[2];
+  int16_t i;
+
+  fac2 (0);
+  fac2 (a2);
+  fac2 (a1);                  // { dg-warning "'fac2' reading 4 bytes from a region of size 2 " }
+  fac2 (&i);                  // { dg-warning "'fac2' reading 4 bytes from a region of size 2 " }
+
+  fxac2 (0);                  // { dg-warning "\\\[-Wnonnull" }
+  fxac2 (a2);
+  fxac2 (a1);                 // { dg-warning "'fxac2' reading 4 bytes from a region of size 2 " }
+  fxac2 (&i);                 // { dg-warning "'fxac2' reading 4 bytes from a region of size 2 " }
+
+  facs2 (0);                  // { dg-warning "\\\[-Wnonnull" }
+  facs2 (a2);
+  facs2 (a1);                 // { dg-warning "'facs2' reading 4 bytes from a region of size 2 " }
+  facs2 (&i);                 // { dg-warning "'facs2' reading 4 bytes from a region of size 2 " }
+
+  fvlac (1, 0);               // { dg-warning "\\\[-Wnonnull" }
+  fvlac (1, &i);
+  fvlac (2, a2);
+  fvlac (2, a1);              // { dg-warning "'fvlac' reading 4 bytes from a region of size 2 " }
+  fvlac (2, &i);              // { dg-warning "'fvlac' reading 4 bytes from a region of size 2 " }
+}
+
+
+void fca3x5 (int16_t[3][5]);
+void fcas5x7 (int16_t[static 5][7]);
+
+struct Snx5 { int16_t a3x5[3][5], a2x5[2][5], a1x5[1][5]; };
+struct Snx7 { int16_t a5x7[5][7], a4x7[4][7], a1x7[1][7]; };
+struct S0x7 { int x; int16_t a0x7[0][7]; };
+
+void test_array_2_dim (struct Snx5 *px5, struct Snx7 *px7, struct S0x7 *p0x7)
+{
+  int16_t a0x5[0][5], a1x5[1][5], a2x5[2][5], a3x5[3][5], a4x5[4][5];
+
+  fca3x5 (a3x5);
+  fca3x5 (a4x5);
+  fca3x5 (a2x5);              // { dg-warning "'fca3x5' accessing 30 bytes in a region of size 20" }
+  fca3x5 (a1x5);              // { dg-warning "'fca3x5' accessing 30 bytes in a region of size 10" }
+  fca3x5 (a0x5);              // { dg-warning "'fca3x5' accessing 30 bytes in a region of size 0" }
+
+  fca3x5 (px5->a3x5);
+  fca3x5 (px5->a2x5);         // { dg-warning "'fca3x5' accessing 30 bytes in a region of size 20" }
+  fca3x5 (px5->a1x5);         // { dg-warning "'fca3x5' accessing 30 bytes in a region of size 10" "pr96346" { xfail *-*-* } }
+
+  {
+    int16_t (*pa2x5)[5] = &a2x5[0];
+    fca3x5 (pa2x5);           // { dg-warning "'fca3x5' accessing 30 bytes in a region of size 10" }
+    ++pa2x5;
+    fca3x5 (pa2x5);           // { dg-warning "'fca3x5' accessing 30 bytes " }
+  }
+
+  int16_t a0x7[0][7], a1x7[1][7], a4x7[4][7], a5x7[5][7], a99x7[99][7];
+  fcas5x7 (a99x7);
+  fcas5x7 (a5x7);
+  fcas5x7 (a4x7);             // { dg-warning "'fcas5x7' accessing 70 bytes in a region of size 56" }
+  fcas5x7 (a1x7);             // { dg-warning "'fcas5x7' accessing 70 bytes in a region of size 14" }
+  fcas5x7 (a0x7);             // { dg-warning "'fcas5x7' accessing 70 bytes in a region of size 0" }
+
+  fcas5x7 (px7->a5x7);
+  fcas5x7 (px7->a4x7);        // { dg-warning "'fcas5x7' accessing 70 bytes in a region of size 56" }
+  fcas5x7 (px7->a1x7);        // { dg-warning "'fcas5x7' accessing 70 bytes in a region of size 14" "pr96346" { xfail *-*-* } }
+
+  fcas5x7 (p0x7->a0x7);       // { dg-warning "'fcas5x7' accessing 70 bytes in a region of size 0" "pr96346" { xfail *-*-* } }
+}
diff --git a/gcc/testsuite/gcc.dg/attr-access-2.c b/gcc/testsuite/gcc.dg/attr-access-2.c
new file mode 100644
index 00000000000..7167c756320
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/attr-access-2.c
@@ -0,0 +1,116 @@
+/* PR 50584 - No warning for passing small array to C99 static array declarator
+   Exercise interaction between explicit attribute access and VLA parameters.
+   { dg-do compile }
+   { dg-options "-Wall" } */
+
+#define RW(...) __attribute__ ((access (read_write, __VA_ARGS__)))
+
+
+void f1 (int n, int[n], int);               // { dg-message "designating the bound of variable length array argument 2" "note" }
+
+// Verify that a redundant attribute access doesn't trigger a warning.
+RW (2, 1) void f1 (int n, int[n], int);
+
+RW (2, 3) void f1 (int n, int[n], int);     // { dg-warning "attribute 'access\\\(read_write, 2, 3\\\)' positional argument 2 conflicts with previous designation by argument 1" }
+
+
+/* Verify that applying the attribute to a VLA with an unspecified bound
+   doesn't trigger any warnings, both with and without a size operand.  */
+          void f2 (int, int[*], int);
+RW (2)    void f2 (int, int[*], int);
+RW (2, 3) void f2 (int, int[*], int);
+
+/* Designating a parameter that comes before the VLA is the same as
+   using the standard VLA int[n] syntax.  It might be worth issuing
+   a portability warning suggesting to prefer the standard syntax.  */
+          void f3 (int, int[*], int);
+RW (2, 1) void f3 (int, int[*], int);
+
+/* Designating a parameter that comes after the VLA cannot be expressed
+   using the standard VLA int[n] syntax.  Verify it doesn't trigger
+   a warning.  */
+          void f4 (int, int[*], int);
+RW (2, 3) void f4 (int, int[*], int);
+
+/* Also verify the same on the same declaration.  */
+          void f5 (int[*], int) RW (1, 2);
+RW (1, 2) void f5 (int[*], int);
+RW (1, 2) void f5 (int[*], int) RW (1, 2);
+
+
+/* Verify that designating a VLA parameter with an explicit bound without
+   also designating the same bound parameter triggers a warning (it has
+   a different meaning).  */
+       void f7 (int n, int[n]);         // { dg-message "21:note: designating the bound of variable length array argument 2" "note" }
+RW (2) void f7 (int n, int[n]);         // { dg-warning "attribute 'access\\\(read_write, 2\\\)' missing positional argument 2 provided in previous designation by argument 1" }
+
+          void f8 (int n, int[n]);
+RW (2, 1) void f8 (int n, int[n]);
+
+
+          void f9 (int, char[]);        // { dg-message "25:note: previously declared as an ordinary array 'char\\\[]'" note" }
+RW (2)    void f9 (int n, char a[n])    // { dg-warning "argument 2 of type 'char\\\[n]' declared as a variable length array" }
+                                        // { dg-warning "attribute 'access *\\\(read_write, 2\\\)' positional argument 2 missing in previous designation" "" { target *-*-* } .-1 }
+                                        // { dg-message "24:note: designating the bound of variable length array argument 2" "note" { target *-*-* } .-2 }
+{ (void)&n; (void)&a; }
+
+
+          void f10 (int, char[]);       // { dg-message "26:note: previously declared as an ordinary array 'char\\\[]'" "note" }
+RW (2, 1) void f10 (int n, char a[n])   // { dg-warning "attribute 'access *\\\(read_write, 2, 1\\\)' positional argument 2 missing in previous designation" "pr????" { xfail *-*-* } }
+                                        // { dg-warning "argument 2 of type 'char\\\[n]' declared as a variable length array"  "" { target *-*-* } .-1 }
+{ (void)&n; (void)&a; }
+
+
+/* The following is diagnosed to point out declarations with the T[*]
+   form in headers where specifying the bound is just as important as
+   in the definition (to detect bugs).  */
+          void f11 (int, char[*]);      // { dg-message "previously declared as 'char\\\[\\\*]' with 1 unspecified variable bound" "note" }
+          void f11 (int m, char a[m]);  // { dg-warning "argument 2 of type 'char\\\[m]' declared with 0 unspecified variable bounds" }
+RW (2, 1) void f11 (int n, char arr[n]) // { dg-warning "argument 2 of type 'char\\\[n]' declared with 0 unspecified variable bounds" }
+{ (void)&n; (void)&arr; }
+
+
+/* Verify that redeclaring a function with attribute access applying
+   to an array parameter of any form is not diagnosed.  */
+          void f12__ (int, int[]) RW (2, 1);
+RW (2, 1) void f12__ (int, int[]);
+
+          void f12_3 (int, int[3]) RW (2, 1);
+RW (2, 1) void f12_3 (int, int[3]);
+
+          void f12_n (int n, int[n]) RW (2, 1);
+RW (2, 1) void f12_n (int n, int[n]);
+
+          void f12_x (int, int[*]) RW (2, 1);
+RW (2, 1) void f12_x (int, int[*]);
+
+          void f13__ (int, int[]);
+RW (2, 1) void f13__ (int, int[]);
+
+          void f13_5 (int, int[5]);
+RW (2, 1) void f13_5 (int, int[5]);
+
+          void f13_n (int n, int[n]);
+RW (2, 1) void f13_n (int n, int[n]);
+
+          void f13_x (int, int[*]);
+RW (2, 1) void f13_x (int, int[*]);
+
+RW (2, 1) void f14__ (int, int[]);
+          void f14__ (int, int[]);
+
+RW (2, 1) void f14_7 (int, int[7]);
+          void f14_7 (int, int[7]);
+
+RW (2, 1) void f14_n (int n, int[n]);
+          void f14_n (int n, int[n]);
+
+RW (2, 1) void f14_x (int, int[*]);
+          void f14_x (int, int[*]);
+
+typedef void G1 (int n, int[n], int);
+
+G1 g1;
+
+RW (2, 3) void g1 (int n, int[n], int);     // { dg-warning "24: attribute 'access *\\\(read_write, 2, 3\\\)' positional argument 2 conflicts with previous designation by argument 3" }
+// { dg-message "designating the bound of variable length array argument 2" "note" { target *-*-* } .-1 }
diff --git a/gcc/testsuite/gcc.dg/attr-access-none.c b/gcc/testsuite/gcc.dg/attr-access-none.c
index d983f2fac06..85f9e73b3f4 100644
--- a/gcc/testsuite/gcc.dg/attr-access-none.c
+++ b/gcc/testsuite/gcc.dg/attr-access-none.c
@@ -23,7 +23,7 @@ void nowarn_fnone_pcv1 (void)
 
 
 int __attribute__ ((access (none, 1, 2)))
-fnone_pcv1_2 (const void*, int);  // { dg-message "in a call to function 'fnone_pcv1_2' declared with attribute 'none \\\(1, 2\\\)'" }
+fnone_pcv1_2 (const void*, int);  // { dg-message "in a call to function 'fnone_pcv1_2' declared with attribute 'access \\\(none, 1, 2\\\)'" "note" }
 
 void nowarn_fnone_pcv1_2 (void)
 {
@@ -34,5 +34,5 @@ void nowarn_fnone_pcv1_2 (void)
 void warn_fnone_pcv1_2 (void)
 {
   char a[3];
-  fnone_pcv1_2 (a, 4);        // { dg-warning "expecting 4 bytes in a region of size 3" }
+  fnone_pcv1_2 (a, 4);        // { dg-warning "expecting 4 or more bytes in a region of size 3" }
 }

^ permalink raw reply	[flat|nested] 33+ messages in thread

* [PATCH 5/5] extend -Warray-bounds to detect out-of-bounds accesses to array parameters
  2020-07-29  1:13 [PATCH 0/5] add checking of function array parameters (PR 50584) Martin Sebor
                   ` (3 preceding siblings ...)
  2020-07-29  1:22 ` [PATCH 4/5] - extend -Wstringop-overflow to detect out-of-bounds accesses to array parameters Martin Sebor
@ 2020-07-29  1:24 ` Martin Sebor
  2020-08-13 16:49   ` Jeff Law
  4 siblings, 1 reply; 33+ messages in thread
From: Martin Sebor @ 2020-07-29  1:24 UTC (permalink / raw)
  To: gcc-patches

[-- Attachment #1: Type: text/plain, Size: 541 bytes --]

Patch 5 adds support for -Warray-bounds to detect out of bounds accesses
in functions that take array/VLA arguments.  The changes also enable
the warning for dynamically allocated memory and with it the detection
of accesses that are only partially out of bounds (e.g., accessing
a four byte int in the last two bytes of a buffer).  In hindsight this
seems independent of the attribute access enhancement so I suppose it
could have been split up into a separate change but I doubt it would
reduce the size of the diff by more than 30 lines.

[-- Attachment #2: gcc-50584-5.diff --]
[-- Type: text/x-patch, Size: 42546 bytes --]

[5/5] - Extend -Warray-bounds to detect out-of-bounds accesses to array parameters.

gcc/ChangeLog:

	PR middle-end/82608
	PR middle-end/94195
	PR c/50584
	PR middle-end/84051
	* gimple-array-bounds.cc (get_base_decl): New function.
	(get_ref_size): New function.
	(trailing_array): New function.
	(array_bounds_checker::check_array_ref): Call them.  Handle arrays
	declared in function parameters.
	(array_bounds_checker::check_mem_ref):  Same.  Handle references to
	dynamically allocated arrays.

gcc/testsuite/ChangeLog:

	PR middle-end/82608
	PR middle-end/94195
	PR c/50584
	PR middle-end/84051
	* gcc.dg/Warray-bounds-63.c: New test.
	* gcc.dg/Warray-bounds-64.c: New test.
	* gcc.dg/Warray-bounds-65.c: New test.
	* gcc.dg/Warray-bounds-66.c: New test.

diff --git a/gcc/gimple-array-bounds.cc b/gcc/gimple-array-bounds.cc
index c2dd6663c3a..b93ef7a7b74 100644
--- a/gcc/gimple-array-bounds.cc
+++ b/gcc/gimple-array-bounds.cc
@@ -36,6 +36,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "vr-values.h"
 #include "domwalk.h"
 #include "tree-cfg.h"
+#include "attribs.h"
+#include "builtins.h"
 
 // This purposely returns a value_range, not a value_range_equiv, to
 // break the dependency on equivalences for this pass.
@@ -46,19 +48,137 @@ array_bounds_checker::get_value_range (const_tree op)
   return ranges->get_value_range (op);
 }
 
+/* Try to determine the DECL that REF refers to.  Return the DECL or
+   the expression closest to it.  Used in informational notes pointing
+   to referenced objects or function parameters.  */
+
+static tree
+get_base_decl (tree ref)
+{
+  tree base = get_base_address (ref);
+  if (DECL_P (base))
+    return base;
+
+  if (TREE_CODE (base) == MEM_REF)
+    base = TREE_OPERAND (base, 0);
+
+  if (TREE_CODE (base) != SSA_NAME)
+    return base;
+
+  do
+    {
+      gimple *def = SSA_NAME_DEF_STMT (base);
+      if (gimple_assign_single_p (def))
+	{
+	  base = gimple_assign_rhs1 (def);
+	  if (TREE_CODE (base) != ASSERT_EXPR)
+	    return base;
+
+	  base = TREE_OPERAND (base, 0);
+	  if (TREE_CODE (base) != SSA_NAME)
+	    return base;
+
+	  continue;
+	}
+
+      if (!gimple_nop_p (def))
+	return base;
+
+      break;
+    } while (true);
+
+  tree var = SSA_NAME_VAR (base);
+  if (TREE_CODE (var) != PARM_DECL)
+    return base;
+
+  return var;
+}
+
+/* Return the constant byte size of the object or type referenced by
+   the MEM_REF ARG.  On success, set *PREF to the DECL or expression
+   ARG refers to.  Otherwise return null.  */
+
+static tree
+get_ref_size (tree arg, tree *pref)
+{
+  if (TREE_CODE (arg) != MEM_REF)
+    return NULL_TREE;
+
+  arg = TREE_OPERAND (arg, 0);
+  tree type = TREE_TYPE (arg);
+  if (!POINTER_TYPE_P (type))
+    return NULL_TREE;
+
+  type = TREE_TYPE (type);
+  if (TREE_CODE (type) != ARRAY_TYPE)
+    return NULL_TREE;
+
+  tree nbytes = TYPE_SIZE_UNIT (type);
+  if (!nbytes || TREE_CODE (nbytes) != INTEGER_CST)
+    return NULL_TREE;
+
+  *pref = get_base_decl (arg);
+  return nbytes;
+}
+
+/* Return true if REF is (likely) an ARRAY_REF to a trailing array member
+   of a struct.  It refines array_at_struct_end_p by detecting a pointer
+   to an array and an array parameter declared using the [N] syntax (as
+   opposed to a pointer) and returning false.  Set *PREF to the decl or
+   expression REF refers to.  */
+
+static bool
+trailing_array (tree arg, tree *pref)
+{
+  tree ref = arg;
+  tree base = get_base_decl (arg);
+  while (TREE_CODE (ref) == ARRAY_REF || TREE_CODE (ref) == MEM_REF)
+    ref = TREE_OPERAND (ref, 0);
+
+  if (TREE_CODE (ref) == COMPONENT_REF)
+    {
+      *pref = TREE_OPERAND (ref, 1);
+      tree type = TREE_TYPE (*pref);
+      if (TREE_CODE (type) == ARRAY_TYPE)
+	{
+	  /* A multidimensional trailing array is not considered special
+	     no matter what its major bound is.  */
+	  type = TREE_TYPE (type);
+	  if (TREE_CODE (type) == ARRAY_TYPE)
+	    return false;
+	}
+    }
+  else
+    *pref = base;
+
+  tree basetype = TREE_TYPE (base);
+  if (TREE_CODE (base) == PARM_DECL
+      && POINTER_TYPE_P (basetype))
+    {
+      tree ptype = TREE_TYPE (basetype);
+      if (TREE_CODE (ptype) == ARRAY_TYPE)
+	return false;
+    }
+
+  return array_at_struct_end_p (arg);
+}
+
 /* Checks one ARRAY_REF in REF, located at LOCUS. Ignores flexible
    arrays and "struct" hacks. If VRP can determine that the array
    subscript is a constant, check if it is outside valid range.  If
    the array subscript is a RANGE, warn if it is non-overlapping with
    valid range.  IGNORE_OFF_BY_ONE is true if the ARRAY_REF is inside
-   a ADDR_EXPR.  Returns true if a warning has been issued.  */
+   a ADDR_EXPR.  Return  true if a warning has been issued or if
+   no-warning is set.  */
 
 bool
 array_bounds_checker::check_array_ref (location_t location, tree ref,
 				       bool ignore_off_by_one)
 {
   if (TREE_NO_WARNING (ref))
-    return false;
+    /* Return true to have the caller prevent warnings for enclosing
+       refs.  */
+    return true;
 
   tree low_sub = TREE_OPERAND (ref, 1);
   tree up_sub = low_sub;
@@ -74,8 +194,7 @@ array_bounds_checker::check_array_ref (location_t location, tree ref,
 
   if (!up_bound
       || TREE_CODE (up_bound) != INTEGER_CST
-      || (warn_array_bounds < 2
-	  && array_at_struct_end_p (ref)))
+      || (warn_array_bounds < 2 && trailing_array (ref, &decl)))
     {
       /* Accesses to trailing arrays via pointers may access storage
 	 beyond the types array bounds.  For such arrays, or for flexible
@@ -116,7 +235,14 @@ array_bounds_checker::check_array_ref (location_t location, tree ref,
 	      poly_int64 off;
 	      if (tree base = get_addr_base_and_unit_offset (arg, &off))
 		{
-		  if (!compref && DECL_P (base))
+		  if (TREE_CODE (base) == MEM_REF)
+		    {
+		      /* Try to determine the size from a pointer to
+			 an array if BASE is one.  */
+		      if (tree size = get_ref_size (base, &decl))
+			maxbound = size;
+		    }
+		  else if (!compref && DECL_P (base))
 		    if (tree basesize = DECL_SIZE_UNIT (base))
 		      if (TREE_CODE (basesize) == INTEGER_CST)
 			{
@@ -217,7 +343,13 @@ array_bounds_checker::check_array_ref (location_t location, tree ref,
 	  fprintf (dump_file, "\n");
 	}
 
-      ref = decl ? decl : TREE_OPERAND (ref, 0);
+      /* Avoid more warnings when checking more significant subscripts
+	 of the same expression.  */
+      ref = TREE_OPERAND (ref, 0);
+      TREE_NO_WARNING (ref) = 1;
+
+      if (decl)
+	ref = decl;
 
       tree rec = NULL_TREE;
       if (TREE_CODE (ref) == COMPONENT_REF)
@@ -235,8 +367,6 @@ array_bounds_checker::check_array_ref (location_t location, tree ref,
 	inform (DECL_SOURCE_LOCATION (ref), "while referencing %qD", ref);
       if (rec && DECL_P (rec))
 	inform (DECL_SOURCE_LOCATION (rec), "defined here %qD", rec);
-
-      TREE_NO_WARNING (ref) = 1;
     }
 
   return warned;
@@ -266,8 +396,8 @@ array_bounds_checker::check_mem_ref (location_t location, tree ref,
 
   const offset_int maxobjsize = tree_to_shwi (max_object_size ());
 
-  /* The array or string constant bounds in bytes.  Initially set
-     to [-MAXOBJSIZE - 1, MAXOBJSIZE]  until a tighter bound is
+  /* The zero-based array or string constant bounds in bytes.  Initially
+     set to [-MAXOBJSIZE - 1, MAXOBJSIZE]  until a tighter bound is
      determined.  */
   offset_int arrbounds[2] = { -maxobjsize - 1, maxobjsize };
 
@@ -275,7 +405,7 @@ array_bounds_checker::check_mem_ref (location_t location, tree ref,
      to be valid, not only does the final offset/subscript must be
      in bounds but all intermediate offsets should be as well.
      GCC may be able to deal gracefully with such out-of-bounds
-     offsets so the checking is only enbaled at -Warray-bounds=2
+     offsets so the checking is only enabled at -Warray-bounds=2
      where it may help detect bugs in uses of the intermediate
      offsets that could otherwise not be detectable.  */
   offset_int ioff = wi::to_offset (fold_convert (ptrdiff_type_node, cstoff));
@@ -284,7 +414,10 @@ array_bounds_checker::check_mem_ref (location_t location, tree ref,
   /* The range of the byte offset into the reference.  */
   offset_int offrange[2] = { 0, 0 };
 
-  const value_range *vr = NULL;
+  /* The statement used to allocate the array or null.  */
+  gimple *alloc_stmt = NULL;
+  /* For an allocation statement, the low bound of the size range.  */
+  offset_int minbound = 0;
 
   /* Determine the offsets and increment OFFRANGE for the bounds of each.
      The loop computes the range of the final offset for expressions such
@@ -294,6 +427,35 @@ array_bounds_checker::check_mem_ref (location_t location, tree ref,
   for (unsigned n = 0; TREE_CODE (arg) == SSA_NAME && n < limit; ++n)
     {
       gimple *def = SSA_NAME_DEF_STMT (arg);
+      if (is_gimple_call (def))
+	{
+	  /* Determine the byte size of the array from an allocation call.  */
+	  wide_int sizrng[2];
+	  if (gimple_call_alloc_size (def, sizrng))
+	    {
+	      arrbounds[0] = 0;
+	      arrbounds[1] = offset_int::from (sizrng[1], UNSIGNED);
+	      minbound = offset_int::from (sizrng[0], UNSIGNED);
+	      alloc_stmt = def;
+	    }
+	  break;
+	}
+
+      if (gimple_nop_p (def))
+	{
+	  /* For a function argument try to determine the byte size
+	     of the array from the current function declaratation
+	     (e.g., attribute access or related).  */
+	  wide_int wr[2];
+	  tree ref = gimple_parm_array_size (arg, wr);
+	  if (!ref)
+	    break;
+	  arrbounds[0] = offset_int::from (wr[0], UNSIGNED);
+	  arrbounds[1] = offset_int::from (wr[1], UNSIGNED);
+	  arg = ref;
+	  break;
+	}
+
       if (!is_gimple_assign (def))
 	break;
 
@@ -316,7 +478,7 @@ array_bounds_checker::check_mem_ref (location_t location, tree ref,
       if (TREE_CODE (varoff) != SSA_NAME)
 	break;
 
-      vr = get_value_range (varoff);
+      const value_range* const vr = get_value_range (varoff);
       if (!vr || vr->undefined_p () || vr->varying_p ())
 	break;
 
@@ -366,79 +528,104 @@ array_bounds_checker::check_mem_ref (location_t location, tree ref,
 	offrange[1] = arrbounds[1];
     }
 
-  if (TREE_CODE (arg) == ADDR_EXPR)
+  tree reftype = NULL_TREE;
+  offset_int eltsize = -1;
+  if (arrbounds[0] >= 0)
+    {
+      /* The byte size of the array has already been determined above
+	 based on a pointer ARG.  Set ELTSIZE to the size of the type
+	 it points to and REFTYPE to the array with the size, rounded
+	 down as necessary.  */
+      reftype = TREE_TYPE (TREE_TYPE (arg));
+      if (TREE_CODE (reftype) == ARRAY_TYPE)
+	reftype = TREE_TYPE (reftype);
+      if (tree refsize = TYPE_SIZE_UNIT (reftype))
+	if (TREE_CODE (refsize) == INTEGER_CST)
+	  eltsize = wi::to_offset (refsize);
+
+      if (eltsize < 0)
+	return false;
+
+      offset_int nelts = arrbounds[1] / eltsize;
+      reftype = build_array_type_nelts (reftype, nelts.to_uhwi ());
+    }
+  else if (TREE_CODE (arg) == ADDR_EXPR)
     {
       arg = TREE_OPERAND (arg, 0);
       if (TREE_CODE (arg) != STRING_CST
 	  && TREE_CODE (arg) != PARM_DECL
 	  && TREE_CODE (arg) != VAR_DECL)
 	return false;
-    }
-  else
-    return false;
 
-  /* The type of the object being referred to.  It can be an array,
-     string literal, or a non-array type when the MEM_REF represents
-     a reference/subscript via a pointer to an object that is not
-     an element of an array.  Incomplete types are excluded as well
-     because their size is not known.  */
-  tree reftype = TREE_TYPE (arg);
-  if (POINTER_TYPE_P (reftype)
-      || !COMPLETE_TYPE_P (reftype)
-      || TREE_CODE (TYPE_SIZE_UNIT (reftype)) != INTEGER_CST)
-    return false;
+      /* The type of the object being referred to.  It can be an array,
+	 string literal, or a non-array type when the MEM_REF represents
+	 a reference/subscript via a pointer to an object that is not
+	 an element of an array.  Incomplete types are excluded as well
+	 because their size is not known.  */
+      reftype = TREE_TYPE (arg);
+      if (POINTER_TYPE_P (reftype)
+	  || !COMPLETE_TYPE_P (reftype)
+	  || TREE_CODE (TYPE_SIZE_UNIT (reftype)) != INTEGER_CST)
+	return false;
 
-  /* Except in declared objects, references to trailing array members
-     of structs and union objects are excluded because MEM_REF doesn't
-     make it possible to identify the member where the reference
-     originated.  */
-  if (RECORD_OR_UNION_TYPE_P (reftype)
-      && (!VAR_P (arg)
-	  || (DECL_EXTERNAL (arg) && array_at_struct_end_p (ref))))
-    return false;
+      /* Except in declared objects, references to trailing array members
+	 of structs and union objects are excluded because MEM_REF doesn't
+	 make it possible to identify the member where the reference
+	 originated.  */
+      if (RECORD_OR_UNION_TYPE_P (reftype)
+	  && (!VAR_P (arg)
+	      || (DECL_EXTERNAL (arg) && array_at_struct_end_p (ref))))
+	return false;
 
-  arrbounds[0] = 0;
+      /* FIXME: Should this be 1 for Fortran?  */
+      arrbounds[0] = 0;
 
-  offset_int eltsize;
-  if (TREE_CODE (reftype) == ARRAY_TYPE)
-    {
-      eltsize = wi::to_offset (TYPE_SIZE_UNIT (TREE_TYPE (reftype)));
-      if (tree dom = TYPE_DOMAIN (reftype))
+      if (TREE_CODE (reftype) == ARRAY_TYPE)
 	{
-	  tree bnds[] = { TYPE_MIN_VALUE (dom), TYPE_MAX_VALUE (dom) };
-	  if (TREE_CODE (arg) == COMPONENT_REF)
+	  /* Set to the size of the array element (and adjust below).  */
+	  eltsize = wi::to_offset (TYPE_SIZE_UNIT (TREE_TYPE (reftype)));
+	  /* Use log2 of size to convert the array byte size in to its
+	     upper bound in elements.  */
+	  const offset_int eltsizelog2 = wi::floor_log2 (eltsize);
+	  if (tree dom = TYPE_DOMAIN (reftype))
 	    {
-	      offset_int size = maxobjsize;
-	      if (tree fldsize = component_ref_size (arg))
-		size = wi::to_offset (fldsize);
-	      arrbounds[1] = wi::lrshift (size, wi::floor_log2 (eltsize));
+	      tree bnds[] = { TYPE_MIN_VALUE (dom), TYPE_MAX_VALUE (dom) };
+	      if (TREE_CODE (arg) == COMPONENT_REF)
+		{
+		  offset_int size = maxobjsize;
+		  if (tree fldsize = component_ref_size (arg))
+		    size = wi::to_offset (fldsize);
+		  arrbounds[1] = wi::lrshift (size, eltsizelog2);
+		}
+	      else if (array_at_struct_end_p (arg) || !bnds[0] || !bnds[1])
+		arrbounds[1] = wi::lrshift (maxobjsize, eltsizelog2);
+	      else
+		arrbounds[1] = (wi::to_offset (bnds[1]) - wi::to_offset (bnds[0])
+				+ 1) * eltsize;
 	    }
-	  else if (array_at_struct_end_p (arg) || !bnds[0] || !bnds[1])
-	    arrbounds[1] = wi::lrshift (maxobjsize, wi::floor_log2 (eltsize));
 	  else
-	    arrbounds[1] = (wi::to_offset (bnds[1]) - wi::to_offset (bnds[0])
-			    + 1) * eltsize;
+	    arrbounds[1] = wi::lrshift (maxobjsize, eltsizelog2);
+
+	  /* Determine a tighter bound of the non-array element type.  */
+	  tree eltype = TREE_TYPE (reftype);
+	  while (TREE_CODE (eltype) == ARRAY_TYPE)
+	    eltype = TREE_TYPE (eltype);
+	  eltsize = wi::to_offset (TYPE_SIZE_UNIT (eltype));
 	}
       else
-	arrbounds[1] = wi::lrshift (maxobjsize, wi::floor_log2 (eltsize));
-
-      /* Determine a tighter bound of the non-array element type.  */
-      tree eltype = TREE_TYPE (reftype);
-      while (TREE_CODE (eltype) == ARRAY_TYPE)
-	eltype = TREE_TYPE (eltype);
-      eltsize = wi::to_offset (TYPE_SIZE_UNIT (eltype));
+	{
+	  eltsize = 1;
+	  tree size = TYPE_SIZE_UNIT (reftype);
+	  if (VAR_P (arg))
+	    if (tree initsize = DECL_SIZE_UNIT (arg))
+	      if (tree_int_cst_lt (size, initsize))
+		size = initsize;
+
+	  arrbounds[1] = wi::to_offset (size);
+	}
     }
   else
-    {
-      eltsize = 1;
-      tree size = TYPE_SIZE_UNIT (reftype);
-      if (VAR_P (arg))
-	if (tree initsize = DECL_SIZE_UNIT (arg))
-	  if (tree_int_cst_lt (size, initsize))
-	    size = initsize;
-
-      arrbounds[1] = wi::to_offset (size);
-    }
+    return false;
 
   offrange[0] += ioff;
   offrange[1] += ioff;
@@ -448,11 +635,25 @@ array_bounds_checker::check_mem_ref (location_t location, tree ref,
      of an array) but always use the stricter bound in diagnostics. */
   offset_int ubound = arrbounds[1];
   if (ignore_off_by_one)
-    ubound += 1;
+    ubound += eltsize;
 
-  if (arrbounds[0] == arrbounds[1]
-      || offrange[0] >= ubound
-      || offrange[1] < arrbounds[0])
+  bool warned = false;
+  /* Set if the lower bound of the subscript is out of bounds.  */
+  const bool lboob = (arrbounds[0] == arrbounds[1]
+		      || offrange[0] >= ubound
+		      || offrange[1] < arrbounds[0]);
+  /* Set if only the upper bound of the subscript is out of bounds.
+     This can happen when using a bigger type to index into an array
+     of a smaller type, as is common with unsigned char.  */
+  tree axstype = TREE_TYPE (ref);
+  offset_int axssize = 0;
+  if (TREE_CODE (axstype) != UNION_TYPE)
+    if (tree access_size = TYPE_SIZE_UNIT (axstype))
+      if (TREE_CODE (access_size) == INTEGER_CST)
+	axssize = wi::to_offset (access_size);
+
+  const bool uboob = !lboob && offrange[0] + axssize > ubound;
+  if (lboob || uboob)
     {
       /* Treat a reference to a non-array object as one to an array
 	 of a single element.  */
@@ -471,8 +672,10 @@ array_bounds_checker::check_mem_ref (location_t location, tree ref,
 	  offrange[0] = offrange[0] / wi::to_offset (size);
 	  offrange[1] = offrange[1] / wi::to_offset (size);
 	}
+    }
 
-      bool warned;
+  if (lboob)
+    {
       if (offrange[0] == offrange[1])
 	warned = warning_at (location, OPT_Warray_bounds,
 			     "array subscript %wi is outside array bounds "
@@ -484,12 +687,66 @@ array_bounds_checker::check_mem_ref (location_t location, tree ref,
 			     "array bounds of %qT",
 			     offrange[0].to_shwi (),
 			     offrange[1].to_shwi (), reftype);
-      if (warned && DECL_P (arg))
+    }
+  else if (uboob && !ignore_off_by_one)
+    {
+      tree backtype = reftype;
+      if (alloc_stmt)
+	/* If the memory was dynamically allocated refer to it as if
+	   it were an untyped array of bytes.  */
+	backtype = build_array_type_nelts (unsigned_char_type_node,
+					   arrbounds[1].to_uhwi ());
+
+      warned = warning_at (location, OPT_Warray_bounds,
+			   "array subscript %<%T[%wi]%> is partly "
+			   "outside array bounds of %qT",
+			   axstype, offrange[0].to_shwi (), backtype);
+    }
+
+  if (warned)
+    {
+      if (DECL_P (arg))
 	inform (DECL_SOURCE_LOCATION (arg), "while referencing %qD", arg);
+      else if (alloc_stmt)
+	{
+	  location_t loc = gimple_location (alloc_stmt);
+	  if (gimple_call_builtin_p (alloc_stmt, BUILT_IN_ALLOCA_WITH_ALIGN))
+	    {
+	      if (minbound == arrbounds[1])
+		inform (loc, "referencing a variable length array "
+			"of size %wu", minbound.to_uhwi ());
+	      else
+		inform (loc, "referencing a variable length array "
+			"of size between %wu and %wu",
+			minbound.to_uhwi (), arrbounds[1].to_uhwi ());
+	    }
+	  else if (tree fndecl = gimple_call_fndecl (alloc_stmt))
+	    {
+	      if (minbound == arrbounds[1])
+		inform (loc, "referencing an object of size %wu "
+			"allocated by %qD",
+			minbound.to_uhwi (), fndecl);
+	      else
+		inform (loc, "referencing an object of size between "
+			"%wu and %wu allocated by %qD",
+			minbound.to_uhwi (), arrbounds[1].to_uhwi (), fndecl);
+	    }
+	  else
+	    {
+	      tree fntype = gimple_call_fntype (alloc_stmt);
+	      if (minbound == arrbounds[1])
+		inform (loc, "referencing an object of size %wu "
+			"allocated by %qT",
+			minbound.to_uhwi (), fntype);
+	      else
+		inform (loc, "referencing an object of size between "
+			"%wu and %wu allocated by %qT",
+			minbound.to_uhwi (), arrbounds[1].to_uhwi (), fntype);
+	    }
+	}
 
-      if (warned)
-	TREE_NO_WARNING (ref) = 1;
-      return warned;
+      TREE_NO_WARNING (ref) = 1;
+      return true;
     }
 
   if (warn_array_bounds < 2)
diff --git a/gcc/testsuite/gcc.dg/Warray-bounds-63.c b/gcc/testsuite/gcc.dg/Warray-bounds-63.c
new file mode 100644
index 00000000000..0583d233c22
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Warray-bounds-63.c
@@ -0,0 +1,53 @@
+/* PR middle-end/94195 - missing warning reading a smaller object via
+   an lvalue of a larger type
+   { dg-do compile }
+   { dg-options "-O2 -Wall" } */
+
+typedef __INT16_TYPE__ int16_t;
+typedef __SIZE_TYPE__  size_t;
+
+void* alloca (size_t);
+
+void sink (void*);
+
+
+void byte_store_to_decl (void)
+{
+  struct S6 { char a[6]; } s;   // { dg-message "referencing 's'" }
+
+  char *p = (char*)&s;
+
+  p[0] = 0; p[1] = 1; p[2] = 2; p[3] = 3; p[4] = 4; p[5] = 5;
+  p[6] = 6;                     // { dg-warning "array subscript 6 is outside array bounds of 'struct S6\\\[1]" }
+
+  sink (&s);
+}
+
+
+void word_store_to_decl (void)
+{
+  struct S6 { char a[6]; } s;   // { dg-message "referencing 's'" }
+
+  char *p = (char*)&s;
+
+  int16_t *q = (int16_t*)(p + 1);
+
+  q[0] = 0; q[1] = 1;
+  q[2] = 2;                     // { dg-warning "array subscript 'int16_t {aka short int}\\\[2]' is partly outside array bounds of 'struct S6\\\[1]'" }
+
+  sink (&s);
+}
+
+
+void word_store_to_alloc (void)
+{
+  struct S6 { char a[6]; } *p;
+  p = alloca (sizeof *p);       // { dg-message "referencing an object of size 6 allocated by 'alloca'" }
+
+  int16_t *q = (int16_t*)((char*)p + 1);
+
+  q[0] = 0; q[1] = 1;
+  q[2] = 2;                     // { dg-warning "array subscript 'int16_t {aka short int}\\\[2]' is partly outside array bounds of 'unsigned char\\\[6]'" }
+
+  sink (p);
+}
diff --git a/gcc/testsuite/gcc.dg/Warray-bounds-64.c b/gcc/testsuite/gcc.dg/Warray-bounds-64.c
new file mode 100644
index 00000000000..88b88debff4
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Warray-bounds-64.c
@@ -0,0 +1,60 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+
+   Verify that out-of-bounds accesses to array arguments are diagnosed,
+   both to ordinary array parameters with constant bounds and to array
+   parameters declared static.  This is the converse of what PR 50584
+   asks for.
+
+   { dg-do compile }
+   { dg-options "-O2 -Wall -Warray-parameter -Wno-vla-paramater" } */
+
+#define NOIPA __attribute__  ((noipa))
+
+void sink (void*, ...);
+
+#define T(...) sink (0, __VA_ARGS__)
+
+
+NOIPA void fca1 (char a[1])
+{
+  T (a[0]);
+  T (a[1]);                   // { dg-warning "-Warray-bounds" }
+}
+
+NOIPA void fcas1 (char a[static 1])
+{
+  T (a[0]);
+  T (a[1]);                   // { dg-warning "-Warray-bounds" }
+}
+
+NOIPA void fca2 (char a[2])
+{
+  T (a[0]); T (a[1]);
+  T (a[2]);                   // { dg-warning "-Warray-bounds" }
+}
+
+NOIPA void fcas2 (char a[static 2])
+{
+  T (a[0]); T (a[1]);
+  T (a[2]);                   // { dg-warning "-Warray-bounds" }
+}
+
+NOIPA void fca3 (char a[3])
+{
+  T (a[0]); T (a[1]); T (a[2]);
+  T (a[3]);                   // { dg-warning "-Warray-bounds" }
+}
+
+NOIPA void fcas3 (char a[static 3])
+{
+  T (a[0]); T (a[1]); T (a[2]);
+  T (a[3]);                   // { dg-warning "-Warray-bounds" }
+}
+
+
+NOIPA void fca1_1 (char a[1][1])
+{
+  T (a[0][0]);
+  T (a[0][1]);                // { dg-warning "-Warray-bounds" }
+}
diff --git a/gcc/testsuite/gcc.dg/Warray-bounds-65.c b/gcc/testsuite/gcc.dg/Warray-bounds-65.c
new file mode 100644
index 00000000000..6bd50d0f876
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Warray-bounds-65.c
@@ -0,0 +1,202 @@
+/* PR middle-end/84051 - missing -Warray-bounds on an out-of-bounds access
+   via an array pointer
+   { dg-do compile }
+   { dg-options "-O2 -Wall -ftrack-macro-expansion=0" } */
+
+void sink (void*, ...);
+#define T(x) sink (0, x)
+
+void
+test_note (int (*pia3)[3])    // { dg-message "while referencing 'pia3'" }
+{
+  int i = 0;
+  T ((*pia3)[i++]);
+  T ((*pia3)[i++]);
+  T ((*pia3)[i++]);
+  T ((*pia3)[i++]);           // { dg-warning "array subscript 3 is (above|outside) array bounds of 'int\\\[3]'" }
+  T ((*pia3)[i++]);           // { dg-warning "array subscript 4 is (above|outside) array bounds of 'int\\\[3]'" }
+
+  {
+    /* Regrettably, the following isn't diagnosed because it's represented
+       the same as the possibly valid access below:
+         MEM[(int *)a_1(D) + 36B] = 0;  */
+    int *p0 = pia3[0];
+    T (p0[3]);                // { dg-warning "array subscript 3 is (above|outside) array bounds of 'int\\\[3]'" "pr?????" { xfail *-*-* } }
+
+    int *p1 = pia3[3];
+    T (p1[0]);                // okay
+  }
+}
+
+void test_a1_cst (_Bool (*pba0)[0], char (*pca1)[1],
+		  short (*psa2)[2], int (*pia3)[3])
+{
+  T ((*pba0)[-1]);            // { dg-warning "array subscript -1 is (above|outside) array bounds of '_Bool\\\[0]'" }
+  T ((*pba0)[0]);             // { dg-warning "array subscript 0 is (above|outside) array bounds of '_Bool\\\[0]'" }
+  T ((*pba0)[1]);             // { dg-warning "array subscript 1 is (above|outside) array bounds of '_Bool\\\[0]'" }
+  T ((*pba0)[2]);             // { dg-warning "array subscript 2 is (above|outside) array bounds of '_Bool\\\[0]'" }
+  T ((*pba0)[12]);            // { dg-warning "array subscript 12 is (above|outside) array bounds of '_Bool\\\[0]'" }
+
+  T ((*pca1)[-1]);            // { dg-warning "array subscript -1 is (below|outside) array bounds of 'char\\\[1]'" }
+  T ((*pca1)[0]);
+  T ((*pca1)[1]);             // { dg-warning "array subscript 1 is (above|outside) array bounds of 'char\\\[1]'" }
+  T ((*pca1)[2]);             // { dg-warning "array subscript 2 is (above|outside) array bounds of 'char\\\[1]'" }
+  T ((*pca1)[123]);           // { dg-warning "array subscript 123 is (above|outside) array bounds of 'char\\\[1]'" }
+
+  T ((*psa2)[-1]);            // { dg-warning "array subscript -1 is (below|outside) array bounds of 'short int\\\[2]'" }
+  T ((*psa2)[0]);
+  T ((*psa2)[1]);
+  T ((*psa2)[2]);             // { dg-warning "array subscript 2 is (above|outside) array bounds of 'short int\\\[2]'" }
+  T ((*psa2)[1234]);          // { dg-warning "array subscript 1234 is (above|outside) array bounds of 'short int\\\[2]'" }
+
+  T ((*pia3)[-1]);            // { dg-warning "array subscript -1 is (below|outside) array bounds of 'int\\\[3]'" }
+  T ((*pia3)[0]);
+  T ((*pia3)[1]);
+  T ((*pia3)[2]);
+  T ((*pia3)[3]);             // { dg-warning "array subscript 3 is (above|outside) array bounds of 'int\\\[3]'" }
+  T ((*pia3)[12345]);         // { dg-warning "array subscript 12345 is (above|outside) array bounds of 'int\\\[3]'" }
+}
+
+
+void test_a2_cst (_Bool (*pba0_1)[0][1], char (*pca1_2)[1][2],
+		  short (*psa2_3)[2][3], int (*pia3_4)[3][4])
+{
+  T ((*pba0_1)[-1][-1]);        // { dg-warning "array subscript -1 is (below|outside) array bounds of '_Bool\\\[1]'" }
+  T ((*pba0_1)[-1][0]);         // { dg-warning "array subscript -1 is (above|outside) array bounds of '_Bool\\\[0]\\\[1]'" }
+
+  T ((*pba0_1)[0][-1]);         // { dg-warning "array subscript -1 is (below|outside) array bounds of '_Bool\\\[1]'" }
+  T ((*pba0_1)[0][0]);          // { dg-warning "array subscript 0 is (above|outside) array bounds of '_Bool\\\[0]\\\[1]'" }
+  T ((*pba0_1)[0][1]);          // { dg-warning "array subscript 1 is (above|outside) array bounds of '_Bool\\\[1]'" }
+  T ((*pba0_1)[0][2]);          // { dg-warning "array subscript 2 is (above|outside) array bounds of '_Bool\\\[1]'" }
+  T ((*pba0_1)[0][12]);         // { dg-warning "array subscript 12 is (above|outside) array bounds of '_Bool\\\[1]'" }
+
+  T ((*pba0_1)[1][-1]);         // { dg-warning "array subscript -1 is (below|outside) array bounds of '_Bool\\\[1]'" }
+  T ((*pba0_1)[1][0]);          // { dg-warning "array subscript 1 is (above|outside) array bounds of '_Bool\\\[0]\\\[1]'" }
+  T ((*pba0_1)[1][1]);          // { dg-warning "array subscript 1 is (above|outside) array bounds of '_Bool\\\[1]'" }
+  T ((*pba0_1)[1][2]);          // { dg-warning "array subscript 2 is (above|outside) array bounds of '_Bool\\\[1]'" }
+  T ((*pba0_1)[1][12]);         // { dg-warning "array subscript 12 is (above|outside) array bounds of '_Bool\\\[1]'" }
+
+
+  T ((*pca1_2)[0][0]);
+  T ((*pca1_2)[0][1]);
+  T ((*pca1_2)[0][2]);          // { dg-warning "array subscript 2 is (above|outside) array bounds of 'char\\\[2]'" }
+
+  T ((*pca1_2)[1][0]);          // { dg-warning "array subscript 1 is (above|outside) array bounds of 'char\\\[1]\\\[2]'" }
+  T ((*pca1_2)[1][1]);          // { dg-warning "array subscript 1 is (above|outside) array bounds of 'char\\\[1]\\\[2]'" }
+  T ((*pca1_2)[1][2]);          // { dg-warning "array subscript 2 is (above|outside) array bounds of 'char\\\[2]'" }
+
+
+  T ((*psa2_3)[0][0]);
+  T ((*psa2_3)[0][1]);
+  T ((*psa2_3)[0][2]);
+  T ((*psa2_3)[0][3]);          // { dg-warning "array subscript 3 is (above|outside) array bounds of 'short int\\\[3]'" }
+
+  T ((*psa2_3)[1][0]);
+  T ((*psa2_3)[1][1]);
+  T ((*psa2_3)[1][2]);
+  T ((*psa2_3)[1][3]);          // { dg-warning "array subscript 3 is (above|outside) array bounds of 'short int\\\[3]'" }
+
+  T ((*psa2_3)[2][0]);          // { dg-warning "array subscript 2 is (above|outside) array bounds of 'short int\\\[2]\\\[3]'" }
+  T ((*psa2_3)[2][1]);          // { dg-warning "array subscript 2 is (above|outside) array bounds of 'short int\\\[2]\\\[3]'" }
+  T ((*psa2_3)[2][2]);          // { dg-warning "array subscript 2 is (above|outside) array bounds of 'short int\\\[2]\\\[3]'" }
+  T ((*psa2_3)[2][3]);          // { dg-warning "array subscript 3 is (above|outside) array bounds of 'short int\\\[3]'" }
+
+
+  T ((*pia3_4)[0][0]);
+  T ((*pia3_4)[0][1]);
+  T ((*pia3_4)[0][2]);
+  T ((*pia3_4)[0][3]);
+  T ((*pia3_4)[0][4]);          // { dg-warning "array subscript 4 is (above|outside) array bounds of 'int\\\[4]'" }
+
+  T ((*pia3_4)[1][0]);
+  T ((*pia3_4)[1][1]);
+  T ((*pia3_4)[1][2]);
+  T ((*pia3_4)[1][3]);
+  T ((*pia3_4)[1][4]);          // { dg-warning "array subscript 4 is (above|outside) array bounds of 'int\\\[4]'" }
+
+  T ((*pia3_4)[2][0]);
+  T ((*pia3_4)[2][1]);
+  T ((*pia3_4)[2][2]);
+  T ((*pia3_4)[2][3]);
+  T ((*pia3_4)[2][4]);          // { dg-warning "array subscript 4 is (above|outside) array bounds of 'int\\\[4]'" }
+
+  T ((*pia3_4)[3][0]);          // { dg-warning "array subscript 3 is (above|outside) array bounds of 'int\\\[3]\\\[4]'" }
+  T ((*pia3_4)[3][1]);          // { dg-warning "array subscript 3 is (above|outside) array bounds of 'int\\\[3]\\\[4]'" }
+  T ((*pia3_4)[3][2]);          // { dg-warning "array subscript 3 is (above|outside) array bounds of 'int\\\[3]\\\[4]'" }
+  T ((*pia3_4)[3][3]);          // { dg-warning "array subscript 3 is (above|outside) array bounds of 'int\\\[3]\\\[4]'" }
+  T ((*pia3_4)[3][4]);          // { dg-warning "array subscript 4 is (above|outside) array bounds of 'int\\\[4]'" }
+}
+
+
+typedef int IA4[4];
+typedef IA4 IA3_4[3];
+
+void test_a2_var (IA3_4 *pia3_4)
+{
+  {
+    IA4 *pia4 = &(*pia3_4)[0];
+
+    T ((*pia4)[-1]);            // { dg-warning "array subscript -1 is (below|outside) array bounds of 'IA4'" }
+    T ((*pia4)[0]);
+    T ((*pia4)[1]);
+    T ((*pia4)[2]);
+    T ((*pia4)[3]);
+    T ((*pia4)[4]);             // { dg-warning "array subscript 4 is (above|outside) array bounds of 'IA4'" }
+  }
+
+  {
+    IA4 *pia4 = &(*pia3_4)[1];
+
+    T ((*pia4)[-1]);            // { dg-warning "array subscript -1 is (below|outside) array bounds of 'IA4'" }
+    T ((*pia4)[0]);
+    T ((*pia4)[1]);
+    T ((*pia4)[2]);
+    T ((*pia4)[3]);
+    T ((*pia4)[4]);             // { dg-warning "array subscript 4 is (above|outside) array bounds of 'IA4'" }
+  }
+
+  {
+    IA4 *pia4 = &(*pia3_4)[2];
+
+    T ((*pia4)[-1]);            // { dg-warning "array subscript -1 is (below|outside) array bounds of 'IA4'" }
+    T ((*pia4)[0]);
+    T ((*pia4)[1]);
+    T ((*pia4)[2]);
+    T ((*pia4)[3]);
+    T ((*pia4)[4]);             // { dg-warning "array subscript 4 is (above|outside) array bounds of 'IA4'" }
+  }
+
+  {
+    IA4 *pia4 = &(*pia3_4)[3];
+
+    T ((*pia4)[-1]);            // { dg-warning "\\\[-Warray-bounds" }
+    /* The following aren't diagnosed unless N itself is out of bounds
+       because thanks to the MEM_REF they're indistinguishable from
+       possibly valid accesses:
+         MEM[(int[4] *)pia3_4_2(D) + 48B][N];  */
+    T ((*pia4)[0]);             // { dg-warning "\\\[-Warray-bounds" "pr?????" { xfail *-*-* } }
+    T ((*pia4)[1]);             // { dg-warning "\\\[-Warray-bounds" "pr?????" { xfail *-*-* } }
+    T ((*pia4)[2]);             // { dg-warning "\\\[-Warray-bounds" "pr?????" { xfail *-*-* } }
+    T ((*pia4)[3]);             // { dg-warning "\\\[-Warray-bounds" "pr?????" { xfail *-*-* } }
+    T ((*pia4)[4]);             // { dg-warning "\\\[-Warray-bounds" }
+  }
+}
+
+
+struct S { IA3_4 *pia3_4; };
+typedef struct S S5[5];
+typedef S5 S5_7[7];
+
+void test_s5_7 (S5_7 *ps5_7)
+{
+  {
+    S5 *ps5 = &(*ps5_7)[0];
+    T ((*ps5)[0]);
+    T ((*(*ps5)[0].pia3_4)[0][0]);
+    T ((*(*ps5)[0].pia3_4)[2][3]);
+    T ((*(*ps5)[0].pia3_4)[2][4]);    // { dg-warning "array subscript 4 is above array bounds of 'IA4'" }
+
+    T ((*(*ps5)[1].pia3_4)[2][3]);
+    T ((*(*ps5)[5].pia3_4)[2][3]);    // { dg-warning "array subscript 5 is above array bounds of 'S5'" }
+  }
+}
diff --git a/gcc/testsuite/gcc.dg/Warray-bounds-66.c b/gcc/testsuite/gcc.dg/Warray-bounds-66.c
new file mode 100644
index 00000000000..d9bb2a29ca4
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Warray-bounds-66.c
@@ -0,0 +1,256 @@
+/* PR middle-end/82608 - missing -Warray-bounds on an out-of-bounds VLA index
+ { dg-do compile }
+ { dg-options "-O2 -Wall -Wno-uninitialized -ftrack-macro-expansion=0" } */
+
+#include "range.h"
+
+typedef __INT16_TYPE__ int16_t;
+
+#define alloca(n) __builtin_alloca (n)
+
+void* calloc (size_t, size_t);
+void* malloc (size_t);
+
+void sink (void*, ...);
+#define sink(...) sink (0, __VA_ARGS__)
+
+#define T(x) (sink (x))
+
+__attribute__ ((alloc_size (1))) void* alloc (size_t);
+
+
+void test_alloca_cst (void)
+{
+  {
+    char *p = alloca (1);
+    sink (p);
+    T (p[0]);
+    T (p[1]);                 // { dg-warning "subscript 1 is outside array bounds of 'char\\\[1\\\]'" }
+  }
+
+  {
+    char *p = alloca (2);
+    sink (p);
+    T (p[0]), T (p[1]);
+    T (p[2]);                 // { dg-warning "subscript 2 is outside array bounds of 'char\\\[2\\\]'" }
+  }
+
+  {
+    char *p = alloca (3);
+    sink (p);
+    T (p[0]), T (p[1]), T (p[2]);
+    T (p[3]);                 // { dg-warning "subscript 3 is outside array bounds of 'char\\\[3\\\]'" }
+  }
+}
+
+
+void test_alloca_char_range (int i, unsigned n, size_t sz)
+{
+  {
+    // Be sure to exercise signed as well as unsigned arguments.
+    char *p = alloca (i);
+    sink (p);
+    T (p[0]), T (p[1]), T (p[12345]);
+    T (p[-1]);                // { dg-warning "subscript -1 is outside array bounds of 'char\\\[" }
+  }
+
+  {
+    char *p = alloca (n);
+    sink (p);
+    T (p[0]), T (p[1]), T (p[12345]);
+    T (p[-1]);                // { dg-warning "subscript -1 is outside array bounds of 'char\\\[" }
+  }
+
+  {
+    char *p = alloca (sz);
+    sink (p);
+    T (p[0]), T (p[1]), T (p[23456]);
+    T (p[-1]);                // { dg-warning "subscript -1 is outside array bounds of 'char\\\[" }
+  }
+
+  {
+    char *p = alloca (UR (0, 1));
+    sink (p);
+    T (p[0]);
+    T (p[1]);                 // { dg-warning "subscript 1 is outside array bounds of 'char\\\[1\\\]'" }
+  }
+
+  {
+    char *p = alloca (UR (0, 2));
+    sink (p);
+    sink (p[0], p[1]);
+    sink (p[2]);              // { dg-warning "subscript 2 is outside array bounds of 'char\\\[2\\\]'" }
+  }
+
+  {
+    char *p = alloca (UR (0, 3));
+    sink (p);
+    T (p[0]), T (p[1]), T (p[2]);
+    T (p[3]);                 // { dg-warning "subscript 3 is outside array bounds of 'char\\\[3\\\]'" }
+  }
+
+  {
+    char *p = alloca (UR (1, 3));
+    sink (p);
+    T (p[0]), T (p[1]), T (p[2]);
+    T (p[3]);                 // { dg-warning "subscript 3 is outside array bounds of 'char\\\[3\\\]'" }
+  }
+
+  {
+    char *p = alloca (UR (2, 3));
+    sink (p);
+    T (p[0]), T (p[1]), T (p[2]);
+    T (p[3]);                 // { dg-warning "subscript 3 is outside array bounds of 'char\\\[3\\\]'" }
+  }
+}
+
+
+void test_alloca_int16_range (unsigned n)
+{
+  int16_t *p;
+  {
+    p = alloca (n);           // { dg-message "allocated by " }
+    sink (p);
+    T (p[0]), T (p[1]), T (p[12345]);
+    T (p[-1]);                // { dg-warning "subscript -1 is outside array bounds of 'int16_t\\\[" }
+  }
+
+  {
+    p = alloca (UR (0, 1));   // { dg-message "object of size between 0 and 1 allocated by '__builtin_alloca'" }
+    sink (p);
+    T (p[0]);                 // { dg-warning "subscript 'int16_t {aka short int}\\\[0\\\]' is partly outside array bounds of 'unsigned char\\\[1]'" }
+    T (p[1]);                 // { dg-warning "subscript 1 is outside array bounds of 'int16_t\\\[0]'" }
+  }
+
+  {
+    p = alloca (UR (0, 2));   // { dg-message "object of size between 0 and 2 allocated by '__builtin_alloca'" }
+    sink (p);
+    sink (p[0]);
+    sink (p[1]);              // { dg-warning "subscript 1 is outside array bounds of 'int16_t\\\[1]'" }
+    sink (p[2]);              // { dg-warning "subscript 2 is outside array bounds of 'int16_t\\\[1\\\]'" }
+  }
+
+  {
+    p = alloca (UR (0, 3));   // { dg-message "object of size between 0 and 3 allocated by '__builtin_alloca'" }
+    sink (p);
+    T (p[0]);
+    T (p[1]);                 // { dg-warning "subscript 'int16_t {aka short int}\\\[1\\\]' is partly outside array bounds of 'unsigned char\\\[3]'" }
+    T (p[2]);                 // { dg-warning "subscript 2 is outside array bounds of 'int16_t\\\[1\\\]'" }
+    T (p[3]);                 // { dg-warning "subscript 3 is outside array bounds of 'int16_t\\\[1\\\]'" }
+  }
+
+  {
+    p = alloca (UR (1, 3));    // { dg-message "object of size between 1 and 3 allocated by '__builtin_alloca'" }
+    sink (p);
+    T (p[0]);
+    T (p[1]);                 // { dg-warning "subscript 'int16_t {aka short int}\\\[1\\\]' is partly outside array bounds of 'unsigned char\\\[3]'" }
+    T (p[2]);                 // { dg-warning "subscript 2 is outside array bounds of 'int16_t\\\[1\\\]'" }
+    T (p[3]);                 // { dg-warning "subscript 3 is outside array bounds of 'int16_t\\\[1\\\]'" }
+  }
+
+  {
+    p = alloca (UR (2, 3));    // { dg-message "object of size between 2 and 3 allocated by '__builtin_alloca'" }
+    sink (p);
+    T (p[0]);
+    T (p[1]);                 // { dg-warning "subscript 'int16_t {aka short int}\\\[1\\\]' is partly outside array bounds of 'unsigned char\\\[3]'" }
+    T (p[2]);                 // { dg-warning "subscript 2 is outside array bounds of 'int16_t\\\[1\\\]'" }
+    T (p[3]);                 // { dg-warning "subscript 3 is outside array bounds of 'int16_t\\\[1\\\]'" }
+  }
+
+  {
+    p = alloca (UR (3, 4));    // { dg-message "object of size between 3 and 4 allocated by '__builtin_alloca'" }
+    sink (p);
+    T (p[0]);
+    T (p[1]);
+    T (p[2]);                 // { dg-warning "subscript 2 is outside array bounds of 'int16_t\\\[2\\\]'" }
+    T (p[3]);                 // { dg-warning "subscript 3 is outside array bounds of 'int16_t\\\[2\\\]'" }
+  }
+}
+
+
+void test_vla_cst (void)
+{
+  int n = 1;
+  {
+    char a[n];
+    sink (a);
+    T (a[0]);
+    T (a[1]);                 // { dg-warning "subscript 1 is (above|outside) array bounds " }
+  }
+
+  {
+    n = 2;
+    char a[n];
+    sink (a);
+    T (a[0]), T (a[1]);
+    T (a[2]);                 // { dg-warning "subscript 2 is (above|outside) array bounds " }
+  }
+
+  {
+    n = 3;
+    char a[n], *p = a;
+    sink (p);
+    T (p[0]), T (p[1]), T (p[2]);
+    T (p[3]);                 // { dg-warning "subscript 3 is (above|outside) array bounds " }
+  }
+}
+
+
+void test_vla_char_range (int i, unsigned n, size_t sz)
+{
+  {
+    char a[i];
+    sink (a);
+    T (a[0]), T (a[1]), T (a[12345]);
+    T (a[-1]);                // { dg-warning "subscript -1 is (below|outside) array bounds of 'char\\\[" }
+  }
+
+  {
+    char a[n];
+    sink (a);
+    T (a[0]), T (a[1]), T (a[12345]);
+    T (a[-1]);                // { dg-warning "subscript -1 is (below|outside) array bounds of 'char\\\[" }
+  }
+
+  {
+    char a[sz];
+    sink (a);
+    T (a[0]), T (a[1]), T (a[23456]);
+    T (a[-1]);                // { dg-warning "subscript -1 is (below|outside) array bounds of 'char\\\[" }
+  }
+
+  {
+    char a[UR (0, 1)];
+    sink (a);
+    T (a[0]);
+    T (a[1]);                 // { dg-warning "subscript 1 is outside array bounds of 'char\\\[1\\\]'" "pr82608" { xfail *-*-* } }
+  }
+
+  {
+    char a[UR (0, 2)];
+    sink (a);
+    sink (a[0], a[1]);
+    sink (a[2]);              // { dg-warning "subscript 2 is outside array bounds of 'char\\\[2\\\]'" "pr82608" { xfail *-*-* } }
+  }
+
+  {
+    char a[UR (0, 3)];
+    sink (a);
+    T (a[0]), T (a[1]), T (a[2]);
+    T (a[3]);                 // { dg-warning "subscript 3 is outside array bounds of 'char\\\[3\\\]'" "pr82608" { xfail *-*-* } }
+  }
+
+  {
+    char a[UR (1, 3)];
+    sink (a);
+    T (a[0]), T (a[1]), T (a[2]);
+    T (a[3]);                 // { dg-warning "subscript 3 is outside array bounds of 'char\\\[3\\\]'" "pr82608" { xfail *-*-* } }
+  }
+
+  {
+    char a[UR (2, 3)];
+    sink (a);
+    T (a[0]), T (a[1]), T (a[2]);
+    T (a[3]);                 // { dg-warning "subscript 3 is outside array bounds of 'char\\\[3\\\]'" "pr82608" { xfail *-*-* } }
+  }
+}
diff --git a/gcc/testsuite/gcc.dg/Warray-bounds-67.c b/gcc/testsuite/gcc.dg/Warray-bounds-67.c
new file mode 100644
index 00000000000..a9b9ff7d2ab
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Warray-bounds-67.c
@@ -0,0 +1,36 @@
+/* Verify warnings fpr accesses to trailing one-element array members
+   of a struct that's a member of either a struct or a union.  Both
+   are obviously undefined but GCC relies on these hacks so the test
+   verifies that -Warray-bounds doesn't trigger for it.
+   { do-do compile }
+   { dg-options "-O2 -Wall" } */
+
+
+typedef union tree_node *tree;
+
+struct tree_exp { int i; tree operands[1]; };
+
+union tree_node
+{
+  struct tree_exp exp;
+};
+
+tree test_nowarn (tree t)
+{
+  return t->exp.operands[3];    // { dg-bogus "\\\[-Warray-bounds" }
+}
+
+
+typedef struct shrub_node *shrub;
+
+struct shrub_exp { int i; shrub operands[1]; };
+
+struct shrub_node
+{
+  struct shrub_exp exp;
+};
+
+shrub test_warn (shrub s)
+{
+  return s->exp.operands[3];    // { dg-warning "\\\[-Warray-bounds" "pr96346" { xfail *-*-* } }
+}

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH 2/5] C front end support to detect out-of-bounds accesses to array parameters
  2020-07-29  1:19 ` [PATCH 2/5] C front end support " Martin Sebor
@ 2020-07-29 18:12   ` Joseph Myers
  2020-08-07 17:01     ` Martin Sebor
  0 siblings, 1 reply; 33+ messages in thread
From: Joseph Myers @ 2020-07-29 18:12 UTC (permalink / raw)
  To: Martin Sebor; +Cc: gcc-patches

On Tue, 28 Jul 2020, Martin Sebor via Gcc-patches wrote:

> +  /* A list of VLA variable bounds or null if not specified.  */
> +  tree vbchain = NULL_TREE;
> +  if (parm->declarator->kind == cdk_array)

> +	  if (pd->kind != cdk_array)
> +	    break;

> +	      /* Skip all constant bounds except the most significant
> +		 one.  The interior ones are included in the array type.  */
> +	      if (next && next->kind == cdk_array)
> +		continue;

Anything working with declarators should typically have logic to skip 
cdk_attrs declarators.

For example, a parameter is declared as an array using [] in that 
declarator if the innermost c_declarator that is not cdk_id or cdk_attrs 
is of kind cdk_array.  (It's the innermost not the outermost because of C 
declarator syntax.)  The array bounds for the parameter array itself (as 
opposed to any other bounds if the parameter is e.g. an array of pointers 
to arrays) are then those in all the cdk_array declarators after the last 
declarator (if any) that's not cdk_array, cdk_attrs or cdk_id (cdk_id only 
comes in the last place).

If e.g. the parameter has the nested cdk_declarator sequence

cdk_function
cdk_pointer
cdk_array
cdk_attrs
cdk_array
cdk_pointer
cdk_array
cdk_attrs
cdk_array
cdk_array
cdk_id

then it's a three-dimensional array of pointers to two-dimensional arrays 
of pointers to functions.

I don't see anything in the tests in this patch to cover this sort of case 
(arrays of pointers, including arrays of pointers to arrays etc.).

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH 2/5] C front end support to detect out-of-bounds accesses to array parameters
  2020-07-29 18:12   ` Joseph Myers
@ 2020-08-07 17:01     ` Martin Sebor
  2020-08-12 23:19       ` Joseph Myers
  0 siblings, 1 reply; 33+ messages in thread
From: Martin Sebor @ 2020-08-07 17:01 UTC (permalink / raw)
  To: Joseph Myers; +Cc: gcc-patches

[-- Attachment #1: Type: text/plain, Size: 2708 bytes --]

On 7/29/20 12:12 PM, Joseph Myers wrote:
> On Tue, 28 Jul 2020, Martin Sebor via Gcc-patches wrote:
> 
>> +  /* A list of VLA variable bounds or null if not specified.  */
>> +  tree vbchain = NULL_TREE;
>> +  if (parm->declarator->kind == cdk_array)
> 
>> +	  if (pd->kind != cdk_array)
>> +	    break;
> 
>> +	      /* Skip all constant bounds except the most significant
>> +		 one.  The interior ones are included in the array type.  */
>> +	      if (next && next->kind == cdk_array)
>> +		continue;
> 
> Anything working with declarators should typically have logic to skip
> cdk_attrs declarators.

Thanks for the quick review/reply and the hints below!

> For example, a parameter is declared as an array using [] in that
> declarator if the innermost c_declarator that is not cdk_id or cdk_attrs
> is of kind cdk_array.  (It's the innermost not the outermost because of C
> declarator syntax.)  The array bounds for the parameter array itself (as
> opposed to any other bounds if the parameter is e.g. an array of pointers
> to arrays) are then those in all the cdk_array declarators after the last
> declarator (if any) that's not cdk_array, cdk_attrs or cdk_id (cdk_id only
> comes in the last place).
> 
> If e.g. the parameter has the nested cdk_declarator sequence
> 
> cdk_function
> cdk_pointer
> cdk_array
> cdk_attrs
> cdk_array
> cdk_pointer
> cdk_array
> cdk_attrs
> cdk_array
> cdk_array
> cdk_id
> 
> then it's a three-dimensional array of pointers to two-dimensional arrays
> of pointers to functions.
> 
> I don't see anything in the tests in this patch to cover this sort of case
> (arrays of pointers, including arrays of pointers to arrays etc.).

I've added a few test cases and reworked the declarator parsing
(get_parm_array_spec) a bit, fixing some bugs.

While testing I also noticed a problem/limitation in the array/VLA
formatting function that I couldn't think of how to fix without
duplicating a lot of what the C/C++ pretty printer does.  So
the updated patch also includes changes to the pretty printer to
do most of what I need.  As may be evident from the comments, I'm
not very happy with the solution but my only other idea was to add
a bit to an array type to indicate whether it's [static N] or [*]
and that seems too intrusive.  If you find the "hack" I put in
unacceptable for the initial patch I'd appreciate a suggestion
for a cleaner approach.  I'd like to  fix that in a followup
patch.

The pretty printer formatting only produces [*] for the most
significant unspecified VLA bound, and the whole machinery
ignores [static] on VLA bounds.  I'd like to fix both but I
thought I'd get your suggestion for how to make [*] appear
in inner bounds first.

Martin

[-- Attachment #2: gcc-50584-2.diff --]
[-- Type: text/x-patch, Size: 59233 bytes --]

[2/5] - C front end support to detect out-of-bounds accesses to array parameters.

gcc/c-family/ChangeLog:

	PR c/50584
	* c-common.h (warn_parm_array_mismatch): Declare new function.
	(has_attribute): Move declaration of an existing function.
	(build_attr_access_from_parms): Declare new function.
	* c.opt (-Warray-parameter, -Wvla-parameter): New options.
	* c-pretty-print.c (pp_c_type_qualifier_list): Don't print array type
	qualifiers here...
	(c_pretty_printer::direct_abstract_declarator): ...but instead print
	them in brackets here.  Also print [static].  Strip extraneous
	expressions from VLA bounds.

gcc/c/ChangeLog:

	PR c/50584
	* c-decl.c (lookup_last_decl): Define new function.
	(c_decl_attributes): Call it.
	(start_decl): Add argument and use it.
	(finish_decl): Call build_attr_access_from_parms and decl_attributes.
	(get_parm_array_spec): Define new function.
	(push_parm_decl): Call get_parm_array_spec.
	(start_function): Call warn_parm_array_mismatch.  Build attribute
	access and add it to current function.
	* c-parser.c (c_parser_declaration_or_fndef): Diagnose mismatches
	in forms of array parameters.
	* c-tree.h (start_decl): Add argument.

gcc/ChangeLog:

	PR c/50584
	* calls.c (initialize_argument_information): Remove assertion.
	* doc/invoke.texi (-Warray-parameter, -Wvla-parameter): Document.
	* tree-pretty-print.c (dump_generic_node): Correct handling of
	qualifiers.

gcc/testsuite/ChangeLog:

	PR c/50584
	* c-c++-common/Warray-bounds-6.c: Correct C++ declaration, adjust
	text of expected diagnostics.
	* gcc.dg/Wbuiltin-declaration-mismatch-9.c: Prune expected warning.
	* gcc.dg/Warray-parameter-2.c: New test.
	* gcc.dg/Warray-parameter-3.c: New test.
	* gcc.dg/Warray-parameter-4.c: New test.
	* gcc.dg/Warray-parameter.c: New test.
	* gcc.dg/Wvla-parameter-2.c: New test.
	* gcc.dg/Wvla-parameter.c: New test.

diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index 61627264e1e..8070ae7f0d1 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -1319,6 +1319,7 @@ extern void c_do_switch_warnings (splay_tree, location_t, tree, tree, bool);
 extern void warn_for_omitted_condop (location_t, tree);
 extern bool warn_for_restrict (unsigned, tree *, unsigned);
 extern void warn_for_address_or_pointer_of_packed_member (tree, tree);
+extern void warn_parm_array_mismatch (location_t, tree, tree);
 
 /* Places where an lvalue, or modifiable lvalue, may be required.
    Used to select diagnostic messages in lvalue_error and
@@ -1373,6 +1374,8 @@ extern tree find_tm_attribute (tree);
 extern const struct attribute_spec::exclusions attr_cold_hot_exclusions[];
 extern const struct attribute_spec::exclusions attr_noreturn_exclusions[];
 extern tree handle_noreturn_attribute (tree *, tree, tree, int, bool *);
+extern bool has_attribute (location_t, tree, tree, tree (*)(tree));
+extern tree build_attr_access_from_parms (tree, bool);
 
 /* In c-format.c.  */
 extern bool valid_format_string_type_p (tree);
@@ -1401,8 +1404,6 @@ extern void maybe_suggest_missing_token_insertion (rich_location *richloc,
 						   location_t prev_token_loc);
 extern tree braced_lists_to_strings (tree, tree);
 
-extern bool has_attribute (location_t, tree, tree, tree (*)(tree));
-
 #if CHECKING_P
 namespace selftest {
   /* Declarations for specific families of tests within c-family,
diff --git a/gcc/c-family/c-pretty-print.c b/gcc/c-family/c-pretty-print.c
index ec0bafe1010..03c89385386 100644
--- a/gcc/c-family/c-pretty-print.c
+++ b/gcc/c-family/c-pretty-print.c
@@ -248,9 +248,12 @@ pp_c_type_qualifier_list (c_pretty_printer *pp, tree t)
   if (!TYPE_P (t))
     t = TREE_TYPE (t);
 
-  qualifiers = TYPE_QUALS (t);
-  pp_c_cv_qualifiers (pp, qualifiers,
-		      TREE_CODE (t) == FUNCTION_TYPE);
+  if (TREE_CODE (t) != ARRAY_TYPE)
+    {
+      qualifiers = TYPE_QUALS (t);
+      pp_c_cv_qualifiers (pp, qualifiers,
+			  TREE_CODE (t) == FUNCTION_TYPE);
+    }
 
   if (!ADDR_SPACE_GENERIC_P (TYPE_ADDR_SPACE (t)))
     {
@@ -572,6 +575,8 @@ c_pretty_printer::abstract_declarator (tree t)
 void
 c_pretty_printer::direct_abstract_declarator (tree t)
 {
+  bool add_space = false;
+
   switch (TREE_CODE (t))
     {
     case POINTER_TYPE:
@@ -585,17 +590,65 @@ c_pretty_printer::direct_abstract_declarator (tree t)
 
     case ARRAY_TYPE:
       pp_c_left_bracket (this);
+
+      if (int quals = TYPE_QUALS (t))
+	{
+	  /* Print the array qualifiers such as in "T[const restrict 3]".  */
+	  pp_c_cv_qualifiers (this, quals, false);
+	  add_space = true;
+	}
+
+      if (tree arr = lookup_attribute ("array", TYPE_ATTRIBUTES (t)))
+	{
+	  if (TREE_VALUE (arr))
+	    {
+	      /* Print the specifier as in "T[static 3]" that's not actually
+		 part of the type but may be added by the front end.  */
+	      pp_c_ws_string (this, "static");
+	      add_space = true;
+	    }
+	  else if (!TYPE_DOMAIN (t))
+	    /* For arrays of unspecified bound using the [*] notation. */
+	    pp_character (this, '*');
+	}
+
       if (tree dom = TYPE_DOMAIN (t))
 	{
 	  if (tree maxval = TYPE_MAX_VALUE (dom))
 	    {
+	      if (add_space)
+		pp_space (this);
+
 	      tree type = TREE_TYPE (maxval);
 
 	      if (tree_fits_shwi_p (maxval))
 		pp_wide_integer (this, tree_to_shwi (maxval) + 1);
-	      else
+	      else if (TREE_CODE (maxval) == INTEGER_CST)
 		expression (fold_build2 (PLUS_EXPR, type, maxval,
 					 build_int_cst (type, 1)));
+	      else
+		{
+		  /* Strip the expressions from around a VLA bound added
+		     internally to make it fit the domain mold, including
+		     any casts.  */
+		  if (TREE_CODE (maxval) == NOP_EXPR)
+		    maxval = TREE_OPERAND (maxval, 0);
+		  if (TREE_CODE (maxval) == PLUS_EXPR
+		      && integer_all_onesp (TREE_OPERAND (maxval, 1)))
+		    {
+		      maxval = TREE_OPERAND (maxval, 0);
+		      if (TREE_CODE (maxval) == NOP_EXPR)
+			maxval = TREE_OPERAND (maxval, 0);
+		    }
+		  if (TREE_CODE (maxval) == SAVE_EXPR)
+		    {
+		      maxval = TREE_OPERAND (maxval, 0);
+		      if (TREE_CODE (maxval) == NOP_EXPR)
+			maxval = TREE_OPERAND (maxval, 0);
+		    }
+
+		  expression (maxval);
+		}
 	    }
 	  else if (TYPE_SIZE (t))
 	    /* Print zero for zero-length arrays but not for flexible
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 2b1aca16eb4..6f3997405a1 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -338,6 +338,14 @@ Warray-bounds=
 LangEnabledBy(C ObjC C++ LTO ObjC++,Wall,1,0)
 ; in common.opt
 
+Warray-parameter
+C ObjC C++ ObjC++ Warning Alias(Warray-parameter=, 2, 0)
+Warn about mismatched declarations of array parameters and unsafe accesses to them.
+
+Warray-parameter=
+C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_array_parameter) IntegerRange(0, 2) LangEnabledBy(C ObjC C++ ObjC++,Wall, 2, 0) Warning
+Warn about mismatched declarations of array parameters and unsafe accesses to them.
+
 Wzero-length-bounds
 C ObjC C++ ObjC++ Var(warn_zero_length_bounds) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall)
 Warn about accesses to interior zero-length array members.
@@ -1253,6 +1261,10 @@ Wno-vla-larger-than
 C ObjC C++ LTO ObjC++ Alias(Wvla-larger-than=,18446744073709551615EiB,none) Warning
 Disable Wvla-larger-than= warning.  Equivalent to Wvla-larger-than=<SIZE_MAX> or larger.
 
+Wvla-parameter
+C ObjC C++ ObjC++ Var(warn_vla_parameter) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall)
+Warn about mismatched declarations of VLA parameters.
+
 Wvolatile
 C++ ObjC++ Var(warn_volatile) Warning
 Warn about deprecated uses of volatile qualifier.
diff --git a/gcc/c/c-decl.c b/gcc/c/c-decl.c
index 5d6b504fe78..dbcdb7d4f91 100644
--- a/gcc/c/c-decl.c
+++ b/gcc/c/c-decl.c
@@ -25,6 +25,7 @@ along with GCC; see the file COPYING3.  If not see
    line numbers.  For example, the CONST_DECLs for enum values.  */
 
 #include "config.h"
+#define INCLUDE_STRING
 #define INCLUDE_UNIQUE_PTR
 #include "system.h"
 #include "coretypes.h"
@@ -4967,6 +4968,17 @@ groktypename (struct c_type_name *type_name, tree *expr,
   return type;
 }
 
+/* Looks up the most recent pushed declaration corresponding to DECL.  */
+
+static tree
+lookup_last_decl (tree decl)
+{
+  tree last_decl = lookup_name (DECL_NAME (decl));
+  if (!last_decl)
+    last_decl = lookup_name_in_scope (DECL_NAME (decl), external_scope);
+  return last_decl;
+}
+
 /* Wrapper for decl_attributes that adds some implicit attributes
    to VAR_DECLs or FUNCTION_DECLs.  */
 
@@ -4995,10 +5007,7 @@ c_decl_attributes (tree *node, tree attributes, int flags)
      so far so that attributes on the current declaration that's
      about to be pushed that conflict with the former can be detected,
      diagnosed, and rejected as appropriate.  */
-  tree last_decl = lookup_name (DECL_NAME (*node));
-  if (!last_decl)
-    last_decl = lookup_name_in_scope (DECL_NAME (*node), external_scope);
-
+  tree last_decl = lookup_last_decl (*node);
   return decl_attributes (node, attributes, flags, last_decl);
 }
 
@@ -5008,6 +5017,8 @@ c_decl_attributes (tree *node, tree attributes, int flags)
    have been parsed, before parsing the initializer if any.
    Here we create the ..._DECL node, fill in its type,
    and put it on the list of decls for the current context.
+   When nonnull, set *LASTLOC to the location of the prior declaration
+   of the same entity if one exists.
    The ..._DECL node is returned as the value.
 
    Exception: for arrays where the length is not specified,
@@ -5020,7 +5031,7 @@ c_decl_attributes (tree *node, tree attributes, int flags)
 
 tree
 start_decl (struct c_declarator *declarator, struct c_declspecs *declspecs,
-	    bool initialized, tree attributes)
+	    bool initialized, tree attributes, location_t *lastloc /* = NULL */)
 {
   tree decl;
   tree tem;
@@ -5038,6 +5049,10 @@ start_decl (struct c_declarator *declarator, struct c_declspecs *declspecs,
   if (!decl || decl == error_mark_node)
     return NULL_TREE;
 
+  if (tree lastdecl = lastloc ? lookup_last_decl (decl) : NULL_TREE)
+    if (lastdecl != error_mark_node)
+      *lastloc = DECL_SOURCE_LOCATION (lastdecl);
+
   if (expr)
     add_stmt (fold_convert (void_type_node, expr));
 
@@ -5475,6 +5490,14 @@ finish_decl (tree decl, location_t init_loc, tree init,
 	  if (asmspec && VAR_P (decl) && C_DECL_REGISTER (decl))
 	    DECL_HARD_REGISTER (decl) = 1;
 	  rest_of_decl_compilation (decl, true, 0);
+
+	  if (TREE_CODE (decl) == FUNCTION_DECL)
+	    {
+	      tree parms = DECL_ARGUMENTS (decl);
+	      const bool builtin = fndecl_built_in_p (decl);
+	      if (tree access = build_attr_access_from_parms (parms, !builtin))
+		decl_attributes (&decl, access, 0);
+	    }
 	}
       else
 	{
@@ -5627,6 +5650,115 @@ grokparm (const struct c_parm *parm, tree *expr)
   return decl;
 }
 
+/* Return attribute "arg spec" corresponding to an array/VLA parameter
+   described by PARM, concatenated onto attributes ATTRS.  No "arg spec"
+   is created for parameters of pointer types, making a distinction
+   between T(*)[N] (or, equivalently, T[][N]) and the T[M][N] form, all
+   of which have the same type and are represented the same, but only
+   the last of which gets an "arg spec" describing the most significant
+   bound M.  */
+
+static tree
+get_parm_array_spec (const struct c_parm *parm, tree attrs)
+{
+  std::string spec;
+
+  /* A list of VLA variable bounds or null if not specified.  */
+  tree vbchain = NULL_TREE;
+
+  /* Create a string representation for the bounds of the array/VLA.  */
+  for (c_declarator *pd = parm->declarator, *next; pd; pd = next)
+    {
+      next = pd->declarator;
+
+      if (pd->kind == cdk_pointer
+	  && (!next || next->kind == cdk_id))
+	{
+	  /* Do nothing for the common case of a pointer.  The fact that
+	     the parameter is one can be deduced from the absence of
+	     an arg spec for it.  */
+	  return attrs;
+	}
+
+      if (pd->kind == cdk_id)
+	{
+	  /* Extract the upper bound from a parameter of an array type.  */
+	  if (parm->specs->type
+	      && TREE_CODE (parm->specs->type) == ARRAY_TYPE
+	      && TYPE_DOMAIN (parm->specs->type)
+	      && TYPE_MAX_VALUE (TYPE_DOMAIN (parm->specs->type)))
+	    {
+	      tree max = TYPE_MAX_VALUE (TYPE_DOMAIN (parm->specs->type));
+	      if (tree_fits_shwi_p (max))
+		{
+		  /* The upper bound is the value of the largest valid index.  */
+		  HOST_WIDE_INT n = tree_to_shwi (max) + 1;
+		  char buf[40];
+		  sprintf (buf, "%lu", (unsigned long)n);
+		  spec += buf;
+		}
+	    }
+	  break;
+	}
+
+      if (pd->kind != cdk_array)
+	continue;
+
+      if (pd->u.array.vla_unspec_p)
+	{
+	  /* Each unspecified bound is represented by a star.  There
+	     can be any number of these in a declaration (but none in
+	     a definition).  */
+	  spec += '*';
+	  continue;
+	}
+
+      tree nelts = pd->u.array.dimen;
+      if (!nelts)
+	{
+	  /* Ordinary array of unspecified size is represented by
+	     a space.  There can be at most one for the most significant
+	     bound.  */
+	  spec += ' ';
+	  break;
+	}
+
+      if (TREE_CODE (nelts) == INTEGER_CST)
+	{
+	  /* Skip all constant bounds except the most significant one.
+	     The interior ones are included in the array type.  */
+	  if (next && (next->kind == cdk_array || next->kind == cdk_pointer))
+	    continue;
+
+	  if (!tree_fits_uhwi_p (nelts))
+	    /* Bail completely on invalid bounds.  */
+	    return attrs;
+
+	  char buf[40];
+	  const char *code = pd->u.array.static_p ? "s" : "";
+	  unsigned HOST_WIDE_INT n = tree_to_uhwi (nelts);
+	  sprintf (buf, "%s%llu", code, (unsigned long long)n);
+	  spec += buf;
+	  break;
+	}
+
+      /* Each variable VLA bound is represented by a dollar sign.  */
+      spec += "$";
+      vbchain = tree_cons (NULL_TREE, nelts, vbchain);
+    }
+
+  if (spec.empty ())
+    return attrs;
+
+  spec.insert (0, "[");
+  spec += ']';
+
+  tree acsstr = build_string (spec.length () + 1, spec.c_str ());
+  tree args = tree_cons (NULL_TREE, acsstr, vbchain);
+  tree name = get_identifier ("arg spec");
+  return tree_cons (name, args, attrs);
+}
+
 /* Given a parsed parameter declaration, decode it into a PARM_DECL
    and push that on the current scope.  EXPR is a pointer to an
    expression that needs to be evaluated for the side effects of array
@@ -5636,12 +5768,12 @@ void
 push_parm_decl (const struct c_parm *parm, tree *expr)
 {
   tree attrs = parm->attrs;
-  tree decl;
-
-  decl = grokdeclarator (parm->declarator, parm->specs, PARM, false, NULL,
-			 &attrs, expr, NULL, DEPRECATED_NORMAL);
+  tree decl = grokdeclarator (parm->declarator, parm->specs, PARM, false, NULL,
+			      &attrs, expr, NULL, DEPRECATED_NORMAL);
   if (decl && DECL_P (decl))
     DECL_SOURCE_LOCATION (decl) = parm->loc;
+
+  attrs = get_parm_array_spec (parm, attrs);
   decl_attributes (&decl, attrs, 0);
 
   decl = pushdecl (decl);
@@ -9227,6 +9359,7 @@ start_function (struct c_declspecs *declspecs, struct c_declarator *declarator,
   old_decl = lookup_name_in_scope (DECL_NAME (decl1), current_scope);
   if (old_decl && TREE_CODE (old_decl) != FUNCTION_DECL)
     old_decl = NULL_TREE;
+
   current_function_prototype_locus = UNKNOWN_LOCATION;
   current_function_prototype_built_in = false;
   current_function_prototype_arg_types = NULL_TREE;
@@ -9357,12 +9490,22 @@ start_function (struct c_declspecs *declspecs, struct c_declarator *declarator,
 		 "%qD is normally a non-static function", decl1);
     }
 
+  tree parms = current_function_arg_info->parms;
+  if (old_decl)
+    {
+      location_t origloc = DECL_SOURCE_LOCATION (old_decl);
+      warn_parm_array_mismatch (origloc, old_decl, parms);
+    }
+
   /* Record the decl so that the function name is defined.
      If we already have a decl for this name, and it is a FUNCTION_DECL,
      use the old decl.  */
 
   current_function_decl = pushdecl (decl1);
 
+  if (tree access = build_attr_access_from_parms (parms, false))
+    decl_attributes (&current_function_decl, access, 0, old_decl);
+
   push_scope ();
   declare_parm_level ();
 
diff --git a/gcc/c/c-parser.c b/gcc/c/c-parser.c
index 7961cbc98bb..f5d18195c14 100644
--- a/gcc/c/c-parser.c
+++ b/gcc/c/c-parser.c
@@ -68,6 +68,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "intl.h"
 #include "c-family/name-hint.h"
 #include "tree-iterator.h"
+#include "tree-pretty-print.h"
 #include "memmodel.h"
 #include "c-family/known-headers.h"
 
@@ -2296,13 +2297,11 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok,
 		  c_parser_skip_to_end_of_block_or_statement (parser);
 		  return;
 		}
-	      tree d = start_decl (declarator, specs, false,
-				   chainon (postfix_attrs,
-					    all_prefix_attrs));
-	      if (d
-		  && TREE_CODE (d) == FUNCTION_DECL
-		  && DECL_ARGUMENTS (d) == NULL_TREE
-		  && DECL_INITIAL (d) == NULL_TREE)
+
+	      location_t lastloc = UNKNOWN_LOCATION;
+	      tree attrs = chainon (postfix_attrs, all_prefix_attrs);
+	      tree d = start_decl (declarator, specs, false, attrs, &lastloc);
+	      if (d && TREE_CODE (d) == FUNCTION_DECL)
 		{
 		  /* Find the innermost declarator that is neither cdk_id
 		     nor cdk_attrs.  */
@@ -2331,10 +2330,18 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok,
 			gcc_unreachable ();
 		      }
 
-		  /* If it exists and is cdk_function, use its parameters.  */
+		  /* If it exists and is cdk_function declaration whose
+		     arguments have not been set yet, use its arguments.  */
 		  if (last_non_id_attrs
 		      && last_non_id_attrs->kind == cdk_function)
-		    DECL_ARGUMENTS (d) = last_non_id_attrs->u.arg_info->parms;
+		    {
+		      tree parms = last_non_id_attrs->u.arg_info->parms;
+		      if (DECL_ARGUMENTS (d) == NULL_TREE
+			  && DECL_INITIAL (d) == NULL_TREE)
+			DECL_ARGUMENTS (d) = parms;
+
+		      warn_parm_array_mismatch (lastloc, d, parms);
+		    }
 		}
 	      if (omp_declare_simd_clauses.exists ())
 		{
@@ -2363,7 +2370,7 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok,
 	      if (d)
 		finish_decl (d, UNKNOWN_LOCATION, NULL_TREE,
 			     NULL_TREE, asm_name);
-	      
+
 	      if (c_parser_next_token_is_keyword (parser, RID_IN))
 		{
 		  if (d)
diff --git a/gcc/c/c-tree.h b/gcc/c/c-tree.h
index 10938cf0857..3adc29dafc4 100644
--- a/gcc/c/c-tree.h
+++ b/gcc/c/c-tree.h
@@ -609,7 +609,7 @@ extern void shadow_tag_warned (const struct c_declspecs *, int);
 extern tree start_enum (location_t, struct c_enum_contents *, tree);
 extern bool start_function (struct c_declspecs *, struct c_declarator *, tree);
 extern tree start_decl (struct c_declarator *, struct c_declspecs *, bool,
-			tree);
+			tree, location_t * = NULL);
 extern tree start_struct (location_t, enum tree_code, tree,
 			  class c_struct_parse_info **);
 extern void store_parm_decls (void);
diff --git a/gcc/calls.c b/gcc/calls.c
index 400dd4fbc92..622417ff74b 100644
--- a/gcc/calls.c
+++ b/gcc/calls.c
@@ -2479,7 +2479,7 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
 	  if (POINTER_TYPE_P (type))
 	    {
 	      access->ptr = args[i].tree_value;
-	      gcc_assert (access->size == NULL_TREE);
+	      // A nonnull ACCESS->SIZE contains VLA bounds.  */
 	    }
 	  else
 	    {
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 7c3cc141c01..91711d23eba 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -5197,6 +5197,7 @@ Options} and @ref{Objective-C and Objective-C++ Dialect Options}.
 
 @gccoptlist{-Waddress   @gol
 -Warray-bounds=1 @r{(only with} @option{-O2}@r{)}  @gol
+-Warray-parameter=2 @r{(C and Objective-C only)} @gol
 -Wbool-compare  @gol
 -Wbool-operation  @gol
 -Wc++11-compat  -Wc++14-compat  @gol
@@ -5248,6 +5249,7 @@ Options} and @ref{Objective-C and Objective-C++ Dialect Options}.
 -Wunused-label     @gol
 -Wunused-value     @gol
 -Wunused-variable  @gol
+-Wvla-parameter @r{(C and Objective-C only)} @gol
 -Wvolatile-register-var  @gol
 -Wzero-length-bounds}
 
@@ -7091,6 +7093,54 @@ pointers. This warning level may give a larger number of
 false positives and is deactivated by default.
 @end table
 
+@item -Warray-parameter
+@itemx -Warray-parameter=@var{n}
+@opindex Wno-array-parameter
+Warn about redeclarations of functions involving arguments of array or
+pointer types of inconsistent kinds or forms, and enable the detection
+of out-of-bounds accesses to such parameters by warnings such as
+@option{-Warray-bounds}.
+
+If the first function declaration uses the array form the bound specified
+in the array is assumed to be the minimum number of elements expected to
+be provided in calls to the function and the maximum number of elements
+accessed by it.  Failing to provide arguments of sufficient size or accessing
+more than the maximum number of elements may be diagnosed by warnings such
+as @option{-Warray-bounds}.  At level 1 the warning diagnoses inconsistencies
+involving array parameters declared using the @code{T[static N]} form.
+
+For example, the warning triggers for the following redeclarations because
+the first one allows an array of any size to be passed to @code{f} while
+the second one with the keyword @code{static} specifies that the array
+argument must have at least four elements.
+
+@smallexample
+void f (int[static 4]);
+void f (int[]);           // warning (inconsistent array form)
+
+void g (void)
+@{
+  int *p = (int *)malloc (4);
+  f (p);                  // warning (array too small)
+  @dots{}
+@}
+@end smallexample
+
+At level 2 the warning also triggers for redeclarations involving any other
+inconsistency in array or pointer argument forms denoting array sizes.
+Pointers and arrays of unspecified bound are considered equivalent and do
+not trigger a warning.
+
+@smallexample
+void g (int*);
+void g (int[]);     // no warning
+void g (int[8]);    // warning (inconsistent array bound)
+@end smallexample
+
+@option{-Warray-parameter=2} is included in @option{-Wall}.  The
+@option{-Wvla-parameter} option triggers warnings for similar inconsistencies
+involving Variable Length Array arguments.
+
 @item -Wattribute-alias=@var{n}
 @itemx -Wno-attribute-alias
 @opindex Wattribute-alias
@@ -8514,6 +8564,44 @@ See also @option{-Walloca-larger-than=@var{byte-size}}.
 Disable @option{-Wvla-larger-than=} warnings.  The option is equivalent
 to @option{-Wvla-larger-than=}@samp{SIZE_MAX} or larger.
 
+@item -Wvla-parameter
+@opindex Wno-vla-parameter
+Warn about redeclarations of functions involving arguments of Variable
+Length Array types of inconsistent kinds or forms, and enable the detection
+of out-of-bounds accesses to such parameters by warnings such as
+@option{-Warray-bounds}.
+
+If the first function declaration uses the VLA form the bound specified
+in the array is assumed to be the minimum number of elements expected to
+be provided in calls to the function and the maximum number of elements
+accessed by it.  Failing to provide arguments of sufficient size or
+accessing more than the maximum number of elements may be diagnosed.
+
+For example, the warning triggers for the following redeclarations because
+the first one allows an array of any size to be passed to @code{f} while
+the second one specifies that the array argument must have at least @code{n}
+elements.  In addition, calling @code{f} with the assotiated VLA bound
+parameter in excess of the actual VLA bound triggers a warning as well.
+
+@smallexample
+void f (int n, int[n]);
+void f (int, int[]);     // warning: argument 2 previously declared as a VLA
+
+void g (int n)
+@{
+    if (n > 4)
+      return;
+    int a[n];
+    f (sizeof a, a);     // warning: access to a by f may be out of bounds
+  @dots{}
+@}
+
+@end smallexample
+
+@option{-Wvla-parameter} is included in @option{-Wall}.  The
+@option{-Warray-parameter} option triggers warnings for similar problems
+involving ordinary array arguments.
+
 @item -Wvolatile-register-var
 @opindex Wvolatile-register-var
 @opindex Wno-volatile-register-var
diff --git a/gcc/testsuite/c-c++-common/Warray-bounds-6.c b/gcc/testsuite/c-c++-common/Warray-bounds-6.c
index 6611d5cd916..5e787360314 100644
--- a/gcc/testsuite/c-c++-common/Warray-bounds-6.c
+++ b/gcc/testsuite/c-c++-common/Warray-bounds-6.c
@@ -1,8 +1,12 @@
-/* PR tree-optimization/86614 */
-/* { dg-do compile } */
-/* { dg-options "-O2 -Warray-bounds" } */
+/* PR tree-optimization/86614 - duplicate -Warray-bounds for a strncpy
+   call with out-of-bounds offset
+   { dg-do compile }
+   { dg-options "-O2 -Warray-bounds" } */
 
-extern char *strncpy (char *, const char *, __SIZE_TYPE__);
+#if __cplusplus
+extern "C"
+#endif
+char *strncpy (char *, const char *, __SIZE_TYPE__);
 
 void sink (void *);
 
@@ -12,7 +16,8 @@ void g (const char *s, unsigned n)
 {
   int i = (char *)a[1].b - (char *)a + 1;
   char *d = a[1].b;
-  /* Ensure the same bug is not diagnosed more than once.  */
-  strncpy (d + i, s, n);	/* { dg-warning "array subscript \[0-9]+ is outside array bounds of" } */
-				/* { dg-bogus "offset \[0-9]+ is out of the bounds \\\[0, \[0-9]+\\\] of object 'a' with type" "" { target *-*-* } .-1 } */
+  /* Verify the bug is diagnosed exactly once, using either form
+     of the warning.  */
+  strncpy (d + i, s, n);	/* { dg-warning "array subscript \[0-9]+ is outside array bounds|offset \[0-9]+ is out of the bounds" } */
+				/* { dg-bogus "offset \[0-9]+ is out of the bounds|array subscript \[0-9]+ is outside array bounds" "" { target *-*-* } .-1 } */
 }
diff --git a/gcc/testsuite/gcc.dg/Warray-parameter-2.c b/gcc/testsuite/gcc.dg/Warray-parameter-2.c
new file mode 100644
index 00000000000..88f20e203ce
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Warray-parameter-2.c
@@ -0,0 +1,45 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+   { dg-do compile }
+   { dg-options "-Wall" } */
+
+// Reduced from Glibc.
+
+typedef struct FILE FILE;
+
+int vfprintf (FILE*, const char*, __builtin_va_list);
+int vfprintf (FILE*, const char*, __builtin_va_list);   // { dg-bogus "-Warray-parameter" }
+int vfprintf (FILE*, const char*, __builtin_va_list);
+
+int vfscanf (FILE*, const char*, __builtin_va_list);
+int vfscanf (FILE*, const char*, __builtin_va_list);    // { dg-bogus "-Warray-parameter" }
+int vfscanf (FILE*, const char*, __builtin_va_list);
+
+
+/* Verify that mismatches in array/to pointer to va_list are still
+   diagnosed.  */
+
+int fva (__builtin_va_list);
+int fva (__builtin_va_list);
+
+int fpva_a1 (__builtin_va_list*);
+int fpva_a1 (__builtin_va_list[1]);     // { dg-warning "\\\[-Warray-parameter" }
+
+int fpva_a_ (__builtin_va_list*);
+int fpva_a_ (__builtin_va_list[]);
+int fpva_a_ (__builtin_va_list*);
+int fpva_a_ (__builtin_va_list[]);
+
+/* Also verify that a mismatch between a pointer and a one-element
+   array are diagnosed.  This is pervasive in Glibc headers but
+   making an exception for it would leave no way to express
+   the requirement that a function take at least one argument
+   by reference.  */
+
+struct __jmp_buf_tag;
+int __sigsetjmp (struct __jmp_buf_tag*, int);
+
+struct __jmp_buf_tag { };
+typedef struct __jmp_buf_tag jmp_buf[1];
+
+int __sigsetjmp (struct __jmp_buf_tag[1], int);   // { dg-warning "\\\[-Warray-parameter" }
diff --git a/gcc/testsuite/gcc.dg/Warray-parameter-3.c b/gcc/testsuite/gcc.dg/Warray-parameter-3.c
new file mode 100644
index 00000000000..cbf3e9339f5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Warray-parameter-3.c
@@ -0,0 +1,89 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+   { dg-do compile }
+   { dg-options "-Wall -Warray-parameter=1" } */
+
+/* Verify that at level 1 mismatches in the bounds of ordinary array
+   parameters don't trigger -Warray-parameter.  */
+void fax (int[]);
+void fax (int[0]);
+void fax (int[1]);
+void fax (int[2]);
+void fax (int[3]);
+
+/* Same as above but starting with an array with a specified bound.  */
+void gax (int[3]);
+void gax (int[2]);
+void gax (int[1]);
+void gax (int[0]);
+void gax (int[]);
+
+/* Same for multidimensional arrays.  */
+void fax_y (int[][3]);
+void fax_y (int[0][3]);
+void fax_y (int[1][3]);
+void fax_y (int[2][3]);
+void fax_y (int[3][3]);
+
+/* Same as above but starting with an array with a specified bound.  */
+void gax_y (int[3][5]);
+void gax_y (int[2][5]);
+void gax_y (int[1][5]);
+void gax_y (int[0][5]);
+void gax_y (int[][5]);
+
+/* Exercise VLAs with a mismatch in the bound for an ordinary array.  */
+void fvlax_y (int n, int[][n]);
+void fvlax_y (int n, int[0][n]);
+void fvlax_y (int n, int[1][n]);
+void fvlax_y (int n, int[2][n]);
+void fvlax_y (int n, int[3][n]);
+
+void fvlaxn_y (int n, int[][n]);
+void fvlaxn_y (int n, int[0][n]);
+void fvlaxn_y (int n, int[1][n]);
+void fvlaxn_y (int n, int[2][n]);
+void fvlaxn_y (int n, int[3][n]);
+
+void fvlaxx_y (int[][*]);
+void fvlaxx_y (int[0][*]);
+void fvlaxx_y (int[1][*]);
+void fvlaxx_y (int[2][*]);
+void fvlaxx_y (int[3][*]);
+
+/* Verify that mismatches in the bounds of array parameters declared
+   static do trigger -Warray-parameter.  */
+void fas1 (int[static 1]);    // { dg-message "previously declared as 'int\\\[static 1]'" }
+void fas1 (int[static 2]);    // { dg-warning "\\\[-Warray-parameter=" }
+
+
+/* Also verify that -Warray-bounds doesn't trigger for ordinary array
+   parameters...  */
+#pragma GCC optimize "2"
+
+__attribute__ ((noipa)) void
+gca3 (char a[3])
+{
+  a[0] = 0; a[1] = 1; a[2] = 2; a[3] = 3;
+}
+
+__attribute__ ((noipa)) void
+gia3 (int a[3])
+{
+  a[0] = 0; a[1] = 1; a[2] = 2; a[3] = 3;
+}
+
+/* ...but does for static arrays.  */
+__attribute__ ((noipa)) void
+gcas3 (char a[static 3])
+{
+  a[0] = 0; a[1] = 1; a[2] = 2;
+  a[3] = 3;                   // { dg-warning "\\\[-Warray-bounds" }
+}
+
+__attribute__ ((noipa)) void
+gias3 (int a[static 3])
+{
+  a[0] = 0; a[1] = 1; a[2] = 2;
+  a[3] = 3;                   // { dg-warning "\\\[-Warray-bounds" }
+}
diff --git a/gcc/testsuite/gcc.dg/Warray-parameter-4.c b/gcc/testsuite/gcc.dg/Warray-parameter-4.c
new file mode 100644
index 00000000000..52de3bc65bf
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Warray-parameter-4.c
@@ -0,0 +1,119 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+
+   Verify warnings for multidimensional arrays.
+
+   { dg-do compile }
+   { dg-options "-Wall -Warray-parameter=2" } */
+
+// Verify that equivalent forms don't tigger a warning.
+
+typedef int IA1[1];
+typedef int IA1x_[][1];
+typedef int IA1x0[0][1];
+
+void fia_x1 (int[][1]);
+void fia_x1 (IA1[0]);
+void fia_x1 (IA1x_);
+void fia_x1 (IA1x0);
+void fia_x1 (int[0][1]);
+void fia_x1 (int[static 0][1]);
+
+// Same as above one more time.
+void fia_x1 (int[][1]);
+void fia_x1 (IA1[0]);
+void fia_x1 (IA1x_);
+void fia_x1 (IA1x0);
+void fia_x1 (int[0][1]);
+void fia_x1 (int[static 0][1]);
+
+
+void fia1x1 (int[1][1]);
+void fia1x1 (int[1][1]);
+void fia1x1 (int[static 1][1]);
+
+void fia2x1 (int[2][1]);
+void fia2x1 (int[2][1]);
+void fia2x1 (int[static 2][1]);
+
+void fia1x2 (int[1][2]);
+void fia1x2 (int[1][2]);
+void fia1x2 (int[static 1][2]);
+
+void fia1x1_2x1 (int[1][1]);          // { dg-message "previously declared as 'int\\\[1]\\\[1]'" }
+void fia1x1_2x1 (int[2][1]);          // { dg-warning "\\\[-Warray-parameter" }
+void fia1x1_2x1 (int[static 1][1]);
+void fia1x1_2x1 (int[static 2][1]);   // { dg-warning "\\\[-Warray-parameter" }
+
+
+void fia2x1_1x1 (int[2][1]);          // { dg-message "previously declared as 'int\\\[2]\\\[1]'" }
+void fia2x1_1x1 (int[1][1]);          // { dg-warning "\\\[-Warray-parameter" }
+void fia2x1_1x1 (int[2][1]);
+void fia2x1_1x1 (int[static 1][1]);   // { dg-warning "\\\[-Warray-parameter" }
+void fia2x1_1x1 (int[static 2][1]);
+
+
+extern int n1, n2;
+
+void fca_xn1 (char[][n1]);
+void fca_xn1 (char[0][n1]);
+void fca_xn1 (char[static 0][n1]);
+
+void fca1xn1_2xn1 (char[1][n1]);
+void fca1xn1_2xn1 (char[2][n1]);        // { dg-warning "\\\[-Warray-parameter" }
+void fca1xn1_2xn1 (char[1][n1]);
+void fca1xn1_2xn1 (char[static 1][n1]);
+void fca1xn1_2xn1 (char[static 2][n1]); // { dg-warning "\\\[-Warray-parameter" }
+
+
+/* Exercise VLAs with a mismatch in the bound for an ordinary array.  */
+void fvlax_y (int n, int[][n]);
+void fvlax_y (int n, int[0][n]);
+void fvlax_y (int n, int[1][n]);        // { dg-warning "argument 2 of type 'int\\\[1]\\\[n]' with mismatched bound" }
+void fvlax_y (int n, int[2][n]);        // { dg-warning "argument 2 of type 'int\\\[2]\\\[n]' with mismatched bound" }
+void fvlax_y (int n, int[3][n]);        // { dg-warning "argument 2 of type 'int\\\[3]\\\[n]' with mismatched bound" }
+
+void fvlaxn_y (int n, int[][n]);
+void fvlaxn_y (int n, int[0][n]);
+void fvlaxn_y (int n, int[1][n]);       // { dg-warning "\\\[-Warray-parameter" }
+void fvlaxn_y (int n, int[2][n]);       // { dg-warning "\\\[-Warray-parameter" }
+void fvlaxn_y (int n, int[3][n]);       // { dg-warning "\\\[-Warray-parameter" }
+
+void fvlaxx_y (int[][*]);
+void fvlaxx_y (int[0][*]);
+void fvlaxx_y (int[1][*]);              // { dg-warning "\\\[-Warray-parameter" }
+void fvlaxx_y (int[2][*]);              // { dg-warning "\\\[-Warray-parameter" }
+void fvlaxx_y (int[3][*]);              // { dg-warning "\\\[-Warray-parameter" }
+
+
+// Verify an array of pointers to an array of function pointers.
+
+void ffpa7_5 (void (* (* (* [7])[5])(void))(void));
+// { dg-message "previously declared as 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[7]\\\)\\\[5]\\\)\\\(void\\\)\\\)\\\(void\\\)'" "note" { target *-*-* } .-1 }
+void ffpa7_5 (void (* (* (* [6])[5])(void))(void));
+// { dg-warning "argument 1 of type 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[6]\\\)\\\[5]\\\)\\\(void\\\)\\\)\\\(void\\\)' with mismatched bound" "" { target *-*-* } .-1 }
+void ffpa7_5 (void (* (* (* [])[5])(void))(void));
+// { dg-warning "argument 1 of type 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[]\\\)\\\[5]\\\)\\\(void\\\)\\\)\\\(void\\\)' with mismatched bound" "" { target *-*-* } .-1 }
+void ffpa7_5 (void (* (* (* (*))[5])(void))(void));
+// { dg-warning "argument 1 of type 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\*\\\)\\\[5]\\\)\\\(void\\\)\\\)\\\(void\\\)' declared as a pointer" "" { target *-*-* } .-1 }
+
+// Same as above but with array of pointers to a VLA of function pointers.
+void ffpa7_n1 (void (* (* (* [7])[n1])(void))(void));
+// { dg-message "previously declared as 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[7]\\\)\\\[n1]\\\)\\\(void\\\)\\\)\\\(void\\\)'" "note" { target *-*-* } .-1 }
+void ffpa7_n1 (void (* (* (* [8])[n1])(void))(void));
+// { dg-warning "argument 1 of type 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[8]\\\)\\\[n1]\\\)\\\(void\\\)\\\)\\\(void\\\)' with mismatched bound" "" { target *-*-* } .-1 }
+
+void ffpa9_x (void (* (* (* [9])[*])(void))(void));
+// { dg-message "previously declared as 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[9]\\\)\\\[\\\*]\\\)\\\(void\\\)\\\)\\\(void\\\)'" "pr?????" { xfail *-*-* } .-1 }
+// { dg-message "previously declared as 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[9]\\\)\\\[0]\\\)\\\(void\\\)\\\)\\\(void\\\)'" "" { target *-*-* } .-2 }
+void ffpa9_x (void (* (* (* [8])[*])(void))(void));
+// { dg-warning "argument 1 of type 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[8]\\\)\\\[\\\*]\\\)\\\(void\\\)\\\)\\\(void\\\)' with mismatched bound" "pr?????" { xfail *-*-* } .-1 }
+// { dg-warning "argument 1 of type 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[8]\\\)\\\[0]\\\)\\\(void\\\)\\\)\\\(void\\\)' with mismatched bound"  "" { target *-*-* } .-2 }
+
+/* Verify a three-dimensional array of pointers to two-dimensional arrays
+   of pointers to function pointers.  */
+
+void ffpa7_5_3 (void (* (* (* (* [7])[5])[3])(void))(void));
+// { dg-message "previously declared as 'void ?\\\(\\\* ?\\\(\\\* ?\\\(\\\* ?\\\(\\\* ?\\\[7]\\\)\\\[5]\\\)\\\[3]\\\)\\\(void\\\)\\\)\\\(void\\\)'" "note" { target *-*-* } .-1 }
+void ffpa7_5_3 (void (* (* (* (* [1])[5])[3])(void))(void));
+// { dg-warning "argument 1 of type 'void ?\\\(\\\* ?\\\(\\\* ?\\\(\\\* ?\\\(\\\* ?\\\[1]\\\)\\\[5]\\\)\\\[3]\\\)\\\(void\\\)\\\)\\\(void\\\)' with mismatched bound" "" { target *-*-* } .-1 }
diff --git a/gcc/testsuite/gcc.dg/Warray-parameter.c b/gcc/testsuite/gcc.dg/Warray-parameter.c
new file mode 100644
index 00000000000..1a6a3afe181
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Warray-parameter.c
@@ -0,0 +1,173 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+
+   Verify that -Warray-parameter diagnoses mismatches in array (and
+   pointer) arrguments between redeclarations of the same function.
+   Also verify that the array/pointer argument form in a mismatched
+   redeclaration doesn't override the form in the initial declaration.
+
+   { dg-do compile }
+   { dg-options "-Wall -Warray-parameter -Wno-vla-paramater" } */
+
+/* Redclarations with the same or equivalent array form should not
+   be dianosed.  T[0] is diagnosed by -Wpedantic for being invalid
+   C so there's little point in also warning for the difference in
+   array form.  */
+void f1vpp (void**);
+void f1vpp (void*[]);
+void f1vpp (void*[0]);
+
+void f1ia_ (int[]);
+void f1ia_ (int[]);
+void f1ia_ (int[0]);
+/* Verify the unused attribute still has an effect.  */
+void f1ia_ (int a[0] __attribute__ ((unused))) { }
+void f1ia_ (int[]);
+
+void f1ia_p (int[]);
+void f1ia_p (int*);
+void f1ia_p (int *p  __attribute__ ((unused))) { }
+void f1ia_p (int[]);
+
+void f1p_ia (const int*);
+void f1p_ia (const int[]);
+void f1p_ia (const int *p __attribute__ ((unused))) { }
+void f1p_ia (const int[]);
+
+void f1ia1 (int[1]);
+void f1ia1 (int[1]);
+void f1ia1 (int[2 - 1]);
+
+void f1ias2 (int[static 2]);
+void f1ias2 (int[static 2]);
+void f1ias2 (int[static 1 + 1]);
+void f1ias2 (int a[static 3 - 1]) { (void)&a; }
+
+void f1ipa_ (int*[]);
+void f1ipa_ (int*[]);
+void f1ipa_ (int*[0]);
+
+void f1ia1_x (int[1]);          // { dg-message "previously declared as 'int\\\[1]'" }
+void f1ia1_x (int[]);           // { dg-warning "argument 1 of type 'int\\\[]' with mismatched bound" }
+void f1ia1_x (int[]);           // { dg-warning "argument 1 of type 'int\\\[]' with mismatched bound" }
+void f1ia1_x (int[1]);
+void f1ia1_x (int[2]);          // { dg-warning "argument 1 of type 'int\\\[2]' with mismatched bound" }
+void f1ia1_x (int[1]);
+void f1ia1_x (int[3]);          // { dg-warning "argument 1 of type 'int\\\[3]' with mismatched bound" }
+void f1ia1_x (int a[1] __attribute__ ((unused))) { }
+
+
+void f1ias2_s3 (int[static 2]); // { dg-message "previously declared as 'int\\\[static 2]'" }
+void f1ias2_s3 (int[static 3]); // { dg-warning "argument 1 of type 'int\\\[static 3]' with mismatched bound" }
+/* Verify the unused attribute still has an effect and doesn't interfere
+   with the warning.  */
+void f1ias2_s3 (int a[static 3] __attribute__ ((unused))) { }  // { dg-warning "argument 1 of type 'int\\\[static 3]' with mismatched bound" }
+
+
+/* Ordinary T[N] and T[static N] forms are both effectively treated
+   the same but strictly have different meanings so they are diagnosed.
+   It might be worth splitting the warning into two levels and having
+   only the higher level treat the ordinary form as T[static N].  */
+
+void f1ia3_s4 (int[3]);         // { dg-message "previously declared as 'int\\\[3]'" }
+void f1ia3_s4 (int[static 4]);  // { dg-warning "argument 1 of type 'int\\\[static 4]' with mismatched bound" }
+void f1ia3_s4 (int[3]);
+
+
+void f1ias4_5 (int[static 4]);  // { dg-message "previously declared as 'int\\\[static 4]'" }
+void f1ias4_5 (int[5]);         // { dg-warning "argument 1 of type 'int\\\[5]' with mismatched bound" }
+void f1ias4_5 (int[static 4]);
+
+
+void f1ia_1 (int[]);            // { dg-message "previously declared as 'int\\\[]'" }
+void f1ia_1 (int[1]);           // { dg-warning "argument 1 of type 'int\\\[1]' with mismatched bound" }
+void f1ia_1 (int[]);
+
+
+void f1ca_ (char[]);            // { dg-message "previously declared as 'char\\\[]'" }
+void f1ca_ (char[2]);           // { dg-warning "argument 1 of type 'char\\\[2]' with mismatched bound" }
+void f1ca_ (char[]);
+
+
+void f1csp (const short*);      // { dg-message "previously declared as 'const short int ?\\\*'" }
+void f1csp (const short[3]);    // { dg-warning "argument 1 of type 'const short int\\\[3]' with mismatched bound" }
+void f1csp (const short*);
+
+
+void f1ia2 (int[2]);            // { dg-message "previously declared as 'int\\\[2]'" }
+void f1ia2 (int[1]);            // { dg-warning "argument 1 of type 'int\\\[1]' with mismatched bound" }
+void f1ia2 (int[2]);
+
+
+void f1cvla2 (const volatile long[3]);  // { dg-message "previously declared as 'const volatile long int\\\[3]'" }
+void f1cvla2 (const volatile long[2]);  // { dg-warning "argument 1 of type 'const volatile long int\\\[2]' with mismatched bound" }
+void f1cvla2 (const volatile long[3]);
+void f1cvla2 (const volatile long[restrict 4]); // { dg-warning "argument 1 of type 'const volatile long int\\\[restrict 4]' with mismatched bound" }
+
+
+void f1afa4 (_Atomic float[3]);         // { dg-message "previously declared as an array '_Atomic float ?\\\[3]'" }
+void f1afa4 (_Atomic float*);           // { dg-warning "argument 1 of type '_Atomic float ?\\\*' declared as a pointer" }
+void f1afa4 (_Atomic float[3]);
+
+void f1ipa1_a2 (int*[1]);       // { dg-message "previously declared as 'int \\\*\\\[1]'" }
+void f1ipa1_a2 (int*[2]);       // { dg-warning "argument 1 of type 'int \\\*\\\[2]' with mismatched bound" }
+void f1ipa1_a2 (int*[1]);
+
+
+typedef int IA1[1];
+typedef int IA2[2];
+typedef int IA3[3];
+
+void f1IA1_A2 (IA1);            // { dg-message "previously declared as 'int\\\[1]'" }
+void f1IA1_A2 (IA2);            // { dg-warning "argument 1 of type 'int\\\[2]' with mismatched bound" }
+void f1IA1_A2 (IA1);
+void f1IA1_A2 (int[2]);         // { dg-warning "argument 1 of type 'int\\\[2]' with mismatched bound" }
+
+
+void f1IA1_A3 (IA1 ia1);        // { dg-message "previously declared as 'int\\\[1]'" }
+void f1IA1_A3 (IA3 ia3);        // { dg-warning "argument 1 of type 'int\\\[3]' with mismatched bound" }
+void f1IA1_A3 (IA1 ia1);
+
+
+void f1IA2_A3 (IA2 a);          // { dg-message "previously declared as 'int\\\[2]'" }
+void f1IA2_A3 (IA3 a);          // { dg-warning "argument 1 of type 'int\\\[3]' with mismatched bound" }
+void f1IA2_A3 (IA2 a);
+
+
+// Verify multiple array arguments.
+
+void f2a2_a3_3_3 (int[2], int[3]);  // { dg-message "previously declared as 'int\\\[2]'" }
+void f2a2_a3_3_3 (int[2], int[3]);
+void f2a2_a3_3_3 (int[3], int[3]);  // { dg-warning "argument 1 of type 'int\\\[3]' with mismatched bound" }
+
+
+void f2a2_a3_2_4 (int[2], int[3]);  // { dg-message "previously declared as 'int\\\[3]'" }
+void f2a2_a3_2_4 (int[2], int[4]);  // { dg-warning "argument 2 of type 'int\\\[4]' with mismatched bound" }
+
+
+/* Verify that pointers to arrays and arrays of arrays are differentiated
+   the same way as pointers and arrays of other types.  */
+typedef int A[1];
+typedef A *PA;
+
+void fpia1 (A*);                // { dg-message "previously declared as 'int ?\\(\\\*\\)\\\[1]'" }
+void fpia1 (A[1]);              // { dg-warning "argument 1 of type 'int\\\[1]\\\[1]' with mismatched bound" }
+void fpia1 (PA);
+void fpia1 (int(*)[1]);
+void fpia1 (int[][1]);
+
+void f1vpa1 (void*[][1]);
+void f1vpa1 (void*[0][1]);
+
+/* Verify that attributes with arrays don't cause unwanted warnings and
+   don't suppress intended ones.  */
+
+#define ALIGN(N)__attribute__ ((aligned (__alignof__ (char[N]))))
+
+void fatipa (int (* ALIGN (3)[2])); // { dg-message "previously declared as 'int \\\*\\\[2]'" }
+void fatipa (int (* ALIGN (4)[2]));
+void fatipa (int (* ALIGN (5)[2]));
+void fatipa (int (* ALIGN (7)[3])); // { dg-warning "argument 1 of type 'int \\\*\\\[3]' with mismatched bound" }
+
+void fatiap (int (* ALIGN (3))[2]);
+void fatiap (int (* ALIGN (5))[2]);
diff --git a/gcc/testsuite/gcc.dg/Wbuiltin-declaration-mismatch-9.c b/gcc/testsuite/gcc.dg/Wbuiltin-declaration-mismatch-9.c
index f0c1ce33267..da767b87700 100644
--- a/gcc/testsuite/gcc.dg/Wbuiltin-declaration-mismatch-9.c
+++ b/gcc/testsuite/gcc.dg/Wbuiltin-declaration-mismatch-9.c
@@ -10,3 +10,6 @@ void a (void)
 	  ""        /* { dg-warning "passing argument 2 of .sscanf. from incompatible pointer type" } */
 	  );
 }
+
+/* The scanf call may also trigger:
+   { dg-prune-output "-Wstringop-overflow" } */
diff --git a/gcc/testsuite/gcc.dg/Wvla-parameter-2.c b/gcc/testsuite/gcc.dg/Wvla-parameter-2.c
new file mode 100644
index 00000000000..fc7946477f6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wvla-parameter-2.c
@@ -0,0 +1,45 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+
+   Verify the -Wvla-parameter warnings correctly diagnose mismatches
+   between VLA arguments with mismatched bounds in redeclarations of
+   the same function.
+
+   { dg-do compile }
+   { dg-options "-Wall -Wvla-parameter" } */
+
+void fmn_a1n_axn (int n, int[1][n]);            // { dg-message "previously declared as 'int\\\[1]\\\[n]' with 1 variable bound" "note" }
+void fmn_a1n_axn (int n, int[*][n]);            // { dg-warning "argument 2 of type 'int\\\[\\\*]\\\[n]' declared with 2 variable bounds" }
+
+
+void fmn_axn_a2n (int n, int[*][n]);            // { dg-message "previously declared as 'int\\\[\\\*]\\\[n]' with 2 variable bounds" "note" }
+void fmn_axn_a2n (int n, int[2][n]);            // { dg-warning "argument 2 of type 'int\\\[2]\\\[n]' declared with 1 variable bound" }
+
+
+void fmn_amn_axn (int m, int n, int[m][n]);     // { dg-message "previously declared as 'int\\\[m]\\\[n]' with 0 unspecified variable bounds" "note" }
+void fmn_amn_axn (int m, int n, int[*][n]);     // { dg-warning "argument 3 of type 'int\\\[\\\*]\\\[n]' declared with 1 unspecified variable bound" }
+
+// Same as above but a different function name.
+void gmn_amn_axn (int m, int n, int[m][n]);     // { dg-message "previously declared as 'int\\\[m]\\\[n]' with 0 unspecified variable bounds" "note" }
+void gmn_amn_axn (int m, int n, int[*][n]);     // { dg-warning "argument 3 of type 'int\\\[\\\*]\\\[n]' declared with 1 unspecified variable bound" }
+
+
+int n1, n2, n3, n4, n5, n6, n7, n8, n9;
+void f (int[n1][2][n3][4][n5][6][n7][8][n9]);   // { dg-message "previously declared as 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[n5]\\\[6]\\\[n7]\\\[8]\\\[n9]' with 0 unspecified variable bounds" "note" }
+                                                // { dg-message "with 5 variable bounds" "note" { target *-*-* } .-1  }
+void f (int[n1][2][n3][4][n5][6][n7][8][n9]);
+
+/* Due to a limitation and because [*] is represented the same as [0]
+   only the most significant array bound is rendered as [*]; the others
+   are rendered as [0].  */
+void f (int[n1][2][n3][4][n5][6][n7][8][*]);    // { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[n5]\\\[6]\\\[n7]\\\[8]\\\[\\\*]' declared with 1 unspecified variable bound" "pr?????" { xfail *-*-* } }
+// { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[n5]\\\[6]\\\[n7]\\\[8]\\\[0]' declared with 1 unspecified variable bound" "pr?????" { target *-*-* } .-1 }
+void f (int[n1][2][n3][4][n5][6][*][8][n9]);   // { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[n5]\\\[6]\\\[\\\*]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" "pr?????" { xfail *-*-* } }
+// { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[n5]\\\[6]\\\[0]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" "pr?????" { target *-*-* } .-1 }
+void f (int[n1][2][n3][4][*][6][n7][8][n9]);   // { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[\\\*]\\\[6]\\\[n7]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" "pr?????" { xfail *-*-*} }
+// { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[0]\\\[6]\\\[n7]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" "pr?????" { target *-*-* } .-1 }
+void f (int[n1][2][*][4][n5][6][n7][8][n9]);   // { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[\\\*]\\\[4]\\\[n5]\\\[6]\\\[n7]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" "pr?????" { xfail *-*-* } }
+// { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[0]\\\[4]\\\[n5]\\\[6]\\\[n7]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" "pr?????" { target *-*-* } .-1 }
+void f (int[*][2][n3][4][n5][6][n7][8][n9]);   // { dg-warning "argument 1 of type 'int\\\[\\\*]\\\[2]\\\[n3]\\\[4]\\\[n5]\\\[6]\\\[n7]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" }
+
+void f (int[n1][n2][n3][n4][n5][n6][n7][n8][n9]);   // { dg-warning "argument 1 of type 'int\\\[n1]\\\[n2]\\\[n3]\\\[n4]\\\[n5]\\\[n6]\\\[n7]\\\[n8]\\\[n9]' declared with 9 variable bounds" }
diff --git a/gcc/testsuite/gcc.dg/Wvla-parameter.c b/gcc/testsuite/gcc.dg/Wvla-parameter.c
new file mode 100644
index 00000000000..c3d7b7fc0d6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wvla-parameter.c
@@ -0,0 +1,149 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+   Verify the -Wvla-parameter warnings correctly diagnose mismatches
+   between VLA and non-VLA arguments in redeclarations of the same
+   function.
+   Also verify that the array/pointer argument form in a mismatched
+   redeclaration doesn't override the form in the initial declaration.
+   { dg-do compile }
+   { dg-options "-Wall -Wvla-parameter" } */
+
+/* Verify that redeclaring an argument as a VLA with an unspecified
+   bound that was first declared as an ordinary array with an unspecified
+   bound triggers a warning.  */
+void f1ia_x (int[]);          // { dg-message "previously declared as an ordinary array 'int\\\[]'" "note" }
+void f1ia_x (int[*]);         // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ia_x (int[]);
+void f1ia_x (int[*]);         // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+/* Also verify that a definition of the same form as the first declaration
+   doesn't trigger a warning and doesn't prevent warnings for subsequent
+   mismatches.  */
+void f1ia_x (int a[]) { (void)&a;}
+void f1ia_x (int[*]);         // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+
+/* Repeat the above but starting with an ordinary array with a constant
+   bound.  */
+void f1ia1x (int[1]);          // { dg-message "previously declared as an ordinary array 'int\\\[1]'" "note" }
+void f1ia1x (int[*]);         // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ia1x (int a[1]) { (void)&a; }
+void f1ia1x (int[1]);
+void f1ia1x (int[*]);         // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+
+void f1ipx (int*);            // { dg-message "previously declared as a pointer 'int ?\\\*'" "note" }
+void f1ipx (int[*]);          // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ipx (int*);
+void f1ipx (int *p) { (void)&p; }
+void f1ipx (int[*]);          // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ipx (int*);
+
+void f2ipx (int*, int*);      // { dg-message "previously declared as a pointer 'int ?\\\*'" "note" }
+void f2ipx (int*, int[*]);    // { dg-warning "argument 2 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f2ipx (int*, int*);
+void f2ipx (int*, int[*]);    // { dg-warning "argument 2 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f2ipx (int *p, int *q) { (void)&p; (void)&q; }
+void f2ipx (int*, int[*]);    // { dg-warning "argument 2 of type 'int\\\[\\\*]' declared as a variable length array" }
+
+void f1ias2x (int[static 2]); // { dg-message "previously declared as an ordinary array 'int\\\[static 2]'" }
+void f1ias2x (int[*]);        // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ias2x (int[static 2]);
+void f1ias2x (int[*]);        // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ias2x (int a[static 2]) { (void)&a; }
+void f1ias2x (int[*]);        // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ias2x (int[static 2]);
+
+extern int nelts;
+
+void f1sa_var (short[]);      // { dg-message "previously declared as an ordinary array 'short int\\\[]'" }
+void f1sa_var (short[nelts]); // { dg-warning "argument 1 of type 'short int\\\[nelts]' declared as a variable length array" }
+void f1sa_var (short[]);
+void f1sa_var (short[nelts]); // { dg-warning "argument 1 of type 'short int\\\[nelts]' declared as a variable length array" }
+void f1sa_var (short a[]) { (void)&a; }
+void f1sa_var (short[nelts]); // { dg-warning "argument 1 of type 'short int\\\[nelts]' declared as a variable length array" }
+void f1sa_var (short[]);
+
+void f1sa_expr (int[]);           // { dg-message "previously declared as an ordinary array 'int\\\[]'" }
+void f1sa_expr (int[nelts + 1]);  // { dg-warning "argument 1 of type 'int\\\[nelts \\\+ 1]' declared as a variable length array" }
+void f1sa_expr (int[]);
+void f1sa_expr (int[nelts * 2]);  // { dg-warning "argument 1 of type 'int\\\[nelts \\\* 2]' declared as a variable length array" }
+void f1sa_expr (int a[]) { (void)&a; }
+void f1sa_expr (int[nelts / 3]);  // { dg-warning "argument 1 of type 'int\\\[nelts / 3]' declared as a variable length array" }
+void f1sa_expr (int[]);
+
+extern int f (int);
+
+void f1ia_f (int[]);          // { dg-message "previously declared as an ordinary array 'int\\\[]'" }
+void f1ia_f (int[f (1)]);     // { dg-warning "argument 1 of type 'int\\\[f *\\\(1\\\)]' declared as a variable length array" }
+void f1ia_f (int[]);
+void f1ia_f (int[f (2)]);     // { dg-warning "argument 1 of type 'int\\\[f *\\\(2\\\)]' declared as a variable length array" }
+void f1ia_f (int a[]) { (void)&a; }
+void f1ia_f (int[f (3)]);     // { dg-warning "argument 1 of type 'int\\\[f *\\\(3\\\)]' declared as a variable length array" }
+void f1ia_f (int[f (4)]);     // { dg-warning "argument 1 of type 'int\\\[f *\\\(4\\\)]' declared as a variable length array" }
+void f1ia_f (int[]);
+
+void f1iaf0_f1 (int[f (0)]);  // { dg-message "previously declared as 'int\\\[f *\\\(0\\\)]'" }
+void f1iaf0_f1 (int[f (1)]);  // { dg-warning "argument 1 of type 'int\\\[f *\\\(1\\\)]' declared with mismatched bound" }
+void f1iaf0_f1 (int[f (0)]);
+void f1iaf0_f1 (int[f (1)]);  // { dg-warning "argument 1 of type 'int\\\[f *\\\(1\\\)]' declared with mismatched bound" }
+void f1iaf0_f1 (int a[f (0)]) { (void)&a; }
+void f1iaf0_f1 (int[f (1)]);  // { dg-warning "argument 1 of type 'int\\\[f *\\\(1\\\)]' declared with mismatched bound" }
+void f1iaf0_f1 (int[f (0)]);
+
+void f1la_ (long[]);         // { dg-message "previously declared as an ordinary array 'long int\\\[]'" }
+void f1la_ (long[nelts]);    // { dg-warning "argument 1 of type 'long int\\\[nelts]' declared as a variable length array" }
+void f1la_ (long[]);
+void f1la_ (long a[nelts])   // { dg-warning "argument 1 of type 'long int\\\[nelts]' declared as a variable length array" }
+{ (void)&a; }
+void f1la_ (long[]);
+
+void f2ca_ (int, char[]);     // { dg-message "previously declared as an ordinary array 'char\\\[]'" }
+void f2ca_ (int n, char[n]);  // { dg-warning "argument 2 of type 'char\\\[n]' declared as a variable length array" }
+void f2ca_ (int, char[]);
+void f2ca_ (int n, char a[n]) // { dg-warning "argument 2 of type 'char\\\[n]' declared as a variable length array" }
+{ (void)&n; (void)&a; }
+
+void f2ia1_f (int n, int[n]);     // { dg-message "previously declared as 'int\\\[n]' with bound argument 1" }
+void f2ia1_f (int,   int[f (0)]); // { dg-warning "argument 2 of type 'int\\\[f *\\\(0\\\)]' declared with mismatched bound 'f *\\\(0\\\)'" }
+void f2ia1_f (int m, int[m]);
+void f2ia1_f (int,   int[f (1)]); // { dg-warning "argument 2 of type 'int\\\[f *\\\(1\\\)]' declared with mismatched bound 'f *\\\(1\\\)'" }
+void f2ia1_f (int x, int a[x]) { (void)&x; (void)&a; }
+void f2ia1_f (int,   int[f (2)]);   // { dg-warning "argument 2 of type 'int\\\[f *\\\(2\\\)]' declared with mismatched bound 'f *\\\(2\\\)'" }
+void f2ia1_f (int y, int[y]);
+
+void f2iaf_1 (int,   int[f (0)]); // { dg-message "previously declared as 'int\\\[f *\\\(0\\\)]'" }
+void f2iaf_1 (int n, int[n]);     // { dg-warning "argument 2 of type 'int\\\[n]' declared with mismatched bound argument 1" }
+void f2iaf_1 (int,   int[f (0)]);
+void f2iaf_1 (int m, int[m]);     // { dg-warning "argument 2 of type 'int\\\[m]' declared with mismatched bound argument 1" }
+void f2iaf_1 (int x, int a[f (0)]) { (void)&x; (void)&a; }
+void f2iaf_1 (int y, int[y]);     // { dg-warning "argument 2 of type 'int\\\[y]' declared with mismatched bound argument 1" }
+
+
+void f3ia1 (int n, int, int[n]);  // { dg-message "previously declared as 'int\\\[n]' with bound argument 1" }
+void f3ia1 (int, int n, int[n]);  // { dg-warning "argument 3 of type 'int\\\[n]' declared with mismatched bound argument 2" }
+void f3ia1 (int n, int, int[n]);
+
+
+extern int g (int);
+
+void f1iaf_g (int[f (1)]);    // { dg-message "previously declared as 'int\\\[f *\\\(1\\\)]'" }
+void f1iaf_g (int[g (1)]);    // { dg-warning "argument 1 of type 'int\\\[g *\\\(1\\\)]' declared with mismatched bound" }
+void f1iaf_g (int[f (1)]);
+
+
+void nrf1iaf_g (int[f (1)]);  // { dg-message "previously declared as 'int\\\[f *\\\(1\\\)]'" }
+__attribute__ ((nonnull))
+void nrf1iaf_g (int[g (1)]);  // { dg-warning "argument 1 of type 'int\\\[g *\\\(1\\\)]' declared with mismatched bound" }
+__attribute__ ((noreturn))
+void nrf1iaf_g (int[f (1)]);
+
+/* Verify that the presence or absence of static with VLA dooesn't cause
+   unwanted warnings.  */
+
+int f2ia1_1 (int n, int [n][n]);            // { sg-message "previously declared as 'int\\\[n]\\\[n]' with bound argument 1" }
+int f2ia1_1 (int n, int[static n][n]);
+int f2ia1_1 (int n, int a[static n][n]) { return sizeof *a; }
+int f2ia1_1 (int n, int[static n + 1][n]);  // { dg-warning "argument 2 of type 'int\\\[n \\\+ 1]\\\[n]' declared with mismatched bound 'n \\\+ 1'" }
+
+int f2ias1_1 (int n, int [static n][n]);    // { dg-message "previously declared as 'int\\\[n]\\\[n]' with bound argument 1" }
+int f2ias1_1 (int n, int[n][n]);
+int f2ias1_1 (int n, int a[++n][n])         // { dg-warning "argument 2 of type 'int\\\[\\\+\\\+n]\\\[n]' declared with mismatched bound ' ?\\+\\+n'" }
+{ return sizeof *a; }
diff --git a/gcc/tree-pretty-print.c b/gcc/tree-pretty-print.c
index be1ed906c1d..9729433b8cf 100644
--- a/gcc/tree-pretty-print.c
+++ b/gcc/tree-pretty-print.c
@@ -1693,9 +1693,9 @@ dump_generic_node (pretty_printer *pp, tree node, int spc, dump_flags_t flags,
 	  pp_string (pp, "atomic ");
 	if (quals & TYPE_QUAL_CONST)
 	  pp_string (pp, "const ");
-	else if (quals & TYPE_QUAL_VOLATILE)
+	if (quals & TYPE_QUAL_VOLATILE)
 	  pp_string (pp, "volatile ");
-	else if (quals & TYPE_QUAL_RESTRICT)
+	if (quals & TYPE_QUAL_RESTRICT)
 	  pp_string (pp, "restrict ");
 
 	if (!ADDR_SPACE_GENERIC_P (TYPE_ADDR_SPACE (node)))

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH 1/5] infrastructure to detect out-of-bounds accesses to array parameters
  2020-07-29  1:16 ` [PATCH 1/5] infrastructure to detect out-of-bounds accesses to array parameters Martin Sebor
@ 2020-08-07 17:08   ` Martin Sebor
  2020-08-13 19:09     ` Jeff Law
  0 siblings, 1 reply; 33+ messages in thread
From: Martin Sebor @ 2020-08-07 17:08 UTC (permalink / raw)
  To: gcc-patches

[-- Attachment #1: Type: text/plain, Size: 1018 bytes --]

On 7/28/20 7:16 PM, Martin Sebor wrote:
> Patch 1 adds the basic infrastructure to support array/VLA bounds
> in attribute access.  It extends the access string specification
> to describe function parameters of array types (including VLAs),
> extends the attr_access class to parse the string and store
> the data in a form that's easy to work with, and implements
> checking of various kinds f mismatches between redeclarations.
> It doesn't actually enable anything new so no new tests are added.

Joseph's comments on patch 2 in the series prompted me to change
how the array (and VLA) function parameters are formatted: instead
of implementing it mostly outside the pretty printer (which, to do
completely correctly, would require reimplementing what the pretty
printer already does) I instead enhanced the pretty printer.  That
let me simplify the formatting done in the helper.  The attached
revision reflects this simplification (the only change from
the original is to attr_access::array_as_string).

Martin

[-- Attachment #2: gcc-50584-1.diff --]
[-- Type: text/x-patch, Size: 58320 bytes --]

[1/5] - Infrastructure to detect out-of-bounds accesses to array parameters.

gcc/ChangeLog:

	PR c/50584
	* attribs.c (decl_attributes): Also pass decl along with type
	attributes to handlers.
	(init_attr_rdwr_indices): Change second argument to attribute chain.
	Handle internal attribute representation in addition to external.
	(get_parm_access): New function.
	(attr_access::to_internal_string): Define new member function.
	(attr_access::to_external_string): Define new member function.
	(attr_access::vla_bounds): Define new member function.
	* attribs.h (struct attr_access): Declare new members.
	(attr_access::from_mode_char): Define new member function.
	(get_parm_access): Declare new function.
	* calls.c (initialize_argument_information): Pass function type
	attributes to init_attr_rdwr_indices.
	* tree-ssa-uninit.c (maybe_warn_pass_by_reference): Same.

gcc/c-family/ChangeLog:

	PR c/50584
	* c-attribs.c (c_common_attribute_table): Add "arg spec" attribute.
	(handle_argspec_attribute): New function.
	(get_argument, get_argument_type): New functions.
	(append_access_attrs): Add overload.  Handle internal attribute
	representation in addition to external.
	(handle_access_attribute): Handle internal attribute representation
	in addition to external.
	(build_attr_access_from_parms): New function.
	* c-warn.c (parm_array_as_string): Define new function.
	(plus_one):  Define new function.
	(warn_parm_array_mismatch): Define new function.

gcc/testsuite/ChangeLog:

	PR c/50584
	* gcc.dg/attr-access-read-write-2.c: Adjust text of expected messages.

diff --git a/gcc/attribs.c b/gcc/attribs.c
index 71dae123af8..43fa9b142ff 100644
--- a/gcc/attribs.c
+++ b/gcc/attribs.c
@@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License
 along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
+#define INCLUDE_STRING
 #include "config.h"
 #include "system.h"
 #include "coretypes.h"
@@ -25,6 +26,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "stringpool.h"
 #include "diagnostic-core.h"
 #include "attribs.h"
+#include "fold-const.h"
 #include "stor-layout.h"
 #include "langhooks.h"
 #include "plugin.h"
@@ -32,6 +34,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "hash-set.h"
 #include "diagnostic.h"
 #include "pretty-print.h"
+#include "tree-pretty-print.h"
 #include "intl.h"
 
 /* Table of the tables of attributes (common, language, format, machine)
@@ -707,10 +710,16 @@ decl_attributes (tree *node, tree attributes, int flags,
 	  int cxx11_flag = (cxx11_attr_p ? ATTR_FLAG_CXX11 : 0);
 
 	  /* Pass in an array of the current declaration followed
-	     by the last pushed/merged declaration if  one exists.
+	     by the last pushed/merged declaration if one exists.
+	     For calls that modify the type attributes of a DECL
+	     and for which *ANODE is *NODE's type, also pass in
+	     the DECL as the third element to use in diagnostics.
 	     If the handler changes CUR_AND_LAST_DECL[0] replace
 	     *ANODE with its value.  */
-	  tree cur_and_last_decl[] = { *anode, last_decl };
+	  tree cur_and_last_decl[3] = { *anode, last_decl };
+	  if (anode != node && DECL_P (*node))
+	    cur_and_last_decl[2] = *node;
+
 	  tree ret = (spec->handler) (cur_and_last_decl, name, args,
 				      flags|cxx11_flag, &no_add_attrs);
 
@@ -2017,65 +2026,295 @@ maybe_diag_alias_attributes (tree alias, tree target)
     }
 }
 
-/* Initialize a mapping for a call to function FNDECL declared with
-   attribute access.  Each attribute positional operand inserts one
-   entry into the mapping with the operand number as the key.  */
+/* Initialize a mapping RWM for a call to a function declared with
+   attribute access in ATTRS.  Each attribute positional operand
+   inserts one entry into the mapping with the operand number as
+   the key.  */
 
 void
-init_attr_rdwr_indices (rdwr_map *rwm, tree fntype)
+init_attr_rdwr_indices (rdwr_map *rwm, tree attrs)
 {
-  if (!fntype)
+  if (!attrs)
     return;
 
-  for (tree access = TYPE_ATTRIBUTES (fntype);
+  for (tree access = attrs;
        (access = lookup_attribute ("access", access));
        access = TREE_CHAIN (access))
     {
       /* The TREE_VALUE of an attribute is a TREE_LIST whose TREE_VALUE
 	 is the attribute argument's value.  */
       tree mode = TREE_VALUE (access);
-      gcc_assert (TREE_CODE (mode) == TREE_LIST);
+      if (!mode)
+	return;
+
+      /* The (optional) list of VLA bounds.  */
+      tree vblist = TREE_CHAIN (mode);
+
       mode = TREE_VALUE (mode);
+      if (TREE_CODE (mode) != STRING_CST)
+	continue;
       gcc_assert (TREE_CODE (mode) == STRING_CST);
 
-      const char *modestr = TREE_STRING_POINTER (mode);
-      for (const char *m = modestr; *m; )
+      for (const char *m = TREE_STRING_POINTER (mode); *m; )
 	{
 	  attr_access acc = { };
 
-	  switch (*m)
+	  /* Skip the internal-only plus sign.  */
+	  if (*m == '+')
+	    ++m;
+
+	  acc.str = m;
+	  acc.mode = acc.from_mode_char (*m);
+	  acc.sizarg = UINT_MAX;
+
+	  const char *end;
+	  acc.ptrarg = strtoul (++m, const_cast<char**>(&end), 10);
+	  m = end;
+
+	  if (*m == '[')
 	    {
-	    case 'r': acc.mode = acc.read_only; break;
-	    case 'w': acc.mode = acc.write_only; break;
-	    case 'x': acc.mode = acc.read_write; break;
-	    case '-': acc.mode = acc.none; break;
-	    default: gcc_unreachable ();
+	      /* Forms containing the square bracket are internal-only
+		 (not specified by an attribute declaration), and used
+		 for various forms of array and VLA parameters.  */
+	      acc.internal_p = true;
+
+	      /* Search to the closing bracket and look at the preceding
+		 code: it determines the form of the most significant
+		 bound of the array.  Others prior to it encode the form
+		 of interior VLA bounds.  They're not of interest here.  */
+	      end = strchr (m, ']');
+	      const char *p = end;
+	      gcc_assert (p);
+
+	      while (ISDIGIT (p[-1]))
+		--p;
+
+	      if (ISDIGIT (*p))
+		{
+		  /* A digit denotes a constant bound (as in T[3]).  */
+		  acc.static_p = p[-1] == 's';
+		  acc.minsize = strtoull (p, NULL, 10);
+		}
+	      else if (' ' == p[-1])
+		{
+		  /* A space denotes an ordinary array of unspecified bound
+		     (as in T[]).  */
+		  acc.minsize = 0;
+		}
+	      else if ('*' == p[-1] || '$' == p[-1])
+		{
+		  /* An asterisk denotes a VLA.  When the closing bracket
+		     is followed by a comma and a dollar sign its bound is
+		     on the list.  Otherwise it's a VLA with an unspecified
+		     bound.  */
+		  acc.minsize = HOST_WIDE_INT_M1U;
+		}
+
+	      m = end + 1;
 	    }
 
-	  char *end;
-	  acc.ptrarg = strtoul (++m, &end, 10);
-	  m = end;
 	  if (*m == ',')
 	    {
-	      acc.sizarg = strtoul (++m, &end, 10);
-	      m = end;
+	      ++m;
+	      do
+		{
+		  if (*m == '$')
+		    {
+		      ++m;
+		      if (!acc.size)
+			{
+			  /* Extract the list of VLA bounds for the current
+			     parameter, store it in ACC.SIZE, and advance
+			     to the list of bounds for the next VLA parameter.
+			  */
+			  acc.size = TREE_VALUE (vblist);
+			  vblist = TREE_CHAIN (vblist);
+			}
+		    }
+
+		  if (ISDIGIT (*m))
+		    {
+		      /* Extract the positional argument.  It's absent
+			 for VLAs whose bound doesn't name a function
+			 parameter.  */
+		      unsigned pos = strtoul (m, const_cast<char**>(&end), 10);
+		      if (acc.sizarg == UINT_MAX)
+			acc.sizarg = pos;
+		      m = end;
+		    }
+		}
+	      while (*m == '$');
+	    }
+
+	  acc.end = m;
+
+	  bool existing;
+	  auto &ref = rwm->get_or_insert (acc.ptrarg, &existing);
+	  if (existing)
+	    {
+	      /* Merge the new spec with the existing.  */
+	      if (acc.minsize == HOST_WIDE_INT_M1U)
+		ref.minsize = HOST_WIDE_INT_M1U;
+
+	      if (acc.sizarg != UINT_MAX)
+		ref.sizarg = acc.sizarg;
+
+	      if (acc.mode)
+		ref.mode = acc.mode;
 	    }
 	  else
-	    acc.sizarg = UINT_MAX;
-
-	  acc.ptr = NULL_TREE;
-	  acc.size = NULL_TREE;
+	    ref = acc;
 
 	  /* Unconditionally add an entry for the required pointer
 	     operand of the attribute, and one for the optional size
 	     operand when it's specified.  */
-	  rwm->put (acc.ptrarg, acc);
 	  if (acc.sizarg != UINT_MAX)
 	    rwm->put (acc.sizarg, acc);
 	}
     }
 }
 
+/* Return the access specification for a function parameter PARM
+   or null if the current function has no such specification.  */
+
+attr_access *
+get_parm_access (rdwr_map &rdwr_idx, tree parm,
+		 tree fndecl /* = current_function_decl */)
+{
+  tree fntype = TREE_TYPE (fndecl);
+  init_attr_rdwr_indices (&rdwr_idx, TYPE_ATTRIBUTES (fntype));
+
+  if (rdwr_idx.is_empty ())
+    return NULL;
+
+  unsigned argpos = 0;
+  tree fnargs = DECL_ARGUMENTS (fndecl);
+  for (tree arg = fnargs; arg; arg = TREE_CHAIN (arg), ++argpos)
+    if (arg == parm)
+      return rdwr_idx.get (argpos);
+
+  return NULL;
+}
+
+/* Return the internal representation as STRING_CST.  Internal positional
+   arguments are zero-based.  */
+
+tree
+attr_access::to_internal_string () const
+{
+  return build_string (end - str, str);
+}
+
+/* Return the human-readable representation of the external attribute
+   specification (as it might appear in the source code) as STRING_CST.
+   External positional arguments are one-based.  */
+
+tree
+attr_access::to_external_string () const
+{
+  char buf[80];
+  gcc_assert (mode != deferred);
+  int len = snprintf (buf, sizeof buf, "access (%s, %u",
+		      mode_names[mode], ptrarg + 1);
+  if (sizarg != UINT_MAX)
+    len += snprintf (buf + len, sizeof buf - len, ", %u", sizarg + 1);
+  strcpy (buf + len, ")");
+  return build_string (len + 2, buf);
+}
+
+/* Return the number of specified VLA bounds and set *nunspec to
+   the number of unspecified ones (those designated by [*]).  */
+
+unsigned
+attr_access::vla_bounds (unsigned *nunspec) const
+{
+  *nunspec = 0;
+  for (const char* p = strrchr (str, ']'); p && *p != '['; --p)
+    if (*p == '*')
+      ++*nunspec;
+  return list_length (size);
+}
+
+
+/* Defined in attr_access.  */
+constexpr char attr_access::mode_chars[];
+constexpr char attr_access::mode_names[][11];
+
+/* Format an array, including a VLA, pointed to by TYPE and used as
+   a function parameter as a human-readable string.  ACC describes
+   an access to the parameter and is used to determine the outermost
+   form of the array including its bound which is otherwise obviated
+   by its decay to pointer.  Return the formatted string.  */
+
+std::string
+attr_access::array_as_string (tree type) const
+{
+  std::string typstr;
+
+  if (type == error_mark_node)
+    return std::string ();
+
+  if (this->str)
+    {
+      /* For array parameters (but not pointers) create an array type
+	 that corresponds to the form of the parameter including its
+	 qualifiers even though they apply to the pointer, not the array
+	 type.  */
+      const bool vla_p = minsize == HOST_WIDE_INT_M1U;
+      tree eltype = TREE_TYPE (type);
+      tree artype;
+
+      tree index_type = NULL_TREE;
+      if (minsize == HOST_WIDE_INT_M1U)
+	{
+	  /* Determine if this is a VLA (an array whose most significant
+	     bound is nonconstant and whose access string has "$]" in it)
+	     extract the bound expression from SIZE.  */
+	  const char *p = end;
+	  for ( ; *p-- != ']'; );
+	  if (*p == '$')
+	    index_type = build_index_type (TREE_VALUE (size));
+	}
+      else  if (minsize)
+	index_type = build_index_type (size_int (minsize - 1));
+
+      artype = build_array_type (eltype, index_type);
+
+      if (static_p || vla_p)
+	{
+	  tree flag = static_p ? integer_one_node : NULL_TREE;
+	  /* Hack: there's no language-independent way to encode
+	     the "static" specifier or the "*" notation in an array type.
+	     Temporarily add an attribute to have the pretty printer add
+	     "static" or "*", and remove it later.  The static notation
+	     is only valid in the most significant bound but [*] can be
+	     used for any bound.  Because [*] is represented the same as
+	     [0] this hack only works for the most significant bound like
+	     static and the others are rendered as [0].  */
+	  tree at = tree_cons (get_identifier ("array"), flag, NULL_TREE);
+	  TYPE_ATTRIBUTES (artype) = at;
+	}
+
+      TYPE_ATOMIC (artype) = TYPE_ATOMIC (type);
+      TYPE_READONLY (artype) = TYPE_READONLY (type);
+      TYPE_RESTRICT (artype) = TYPE_RESTRICT (type);
+      TYPE_VOLATILE (artype) = TYPE_VOLATILE (type);
+      type = artype;
+    }
+
+  /* Format the type using the current pretty printer.  The generic tree
+     printer does a terrible job.  */
+  pretty_printer *pp = global_dc->printer->clone ();
+  pp_printf (pp, "%T", type);
+  typstr = pp_formatted_text (pp);
+  delete pp;
+
+  if (this->str)
+    /* Remove the attribute that wasn't installed by decl_attributes.  */
+    TYPE_ATTRIBUTES (type) = NULL_TREE;
+
+  return typstr;
+}
 
 #if CHECKING_P
 
diff --git a/gcc/attribs.h b/gcc/attribs.h
index dea0b6c44e6..d86672795fe 100644
--- a/gcc/attribs.h
+++ b/gcc/attribs.h
@@ -224,20 +224,91 @@ lookup_attribute_by_prefix (const char *attr_name, tree list)
 
 struct attr_access
 {
+  /* The beginning and end of the internal string representation.  */
+  const char *str, *end;
   /* The attribute pointer argument.  */
   tree ptr;
-  /* The size of the pointed-to object or NULL when not specified.  */
+  /* For a declaration, a TREE_CHAIN of VLA bound expressions stored
+     in TREE_VALUE and their positions in the argument list (stored
+     in TREE_PURPOSE).  Each expression may be a PARM_DECL or some
+     other DECL (for ordinary variables), or an EXPR for other
+     expressions (e.g., funcion calls).  */
   tree size;
 
-  /* The zero-based number of each of the formal function arguments.  */
+  /* The zero-based position of each of the formal function arguments.
+     For the optional SIZARG, UINT_MAX when not specified.  For VLAs
+     with multiple variable bounds, SIZARG is the position corresponding
+     to the most significant bound in the argument list.  Positions of
+     subsequent bounds are in the TREE_PURPOSE field of the SIZE chain.  */
   unsigned ptrarg;
   unsigned sizarg;
+  /* For internal specifications only, the constant minimum size of
+     the array, zero if not specified, and HWI_M1U for the unspecified
+     VLA [*] notation.  Meaningless for external (explicit) access
+     specifications.  */
+  unsigned HOST_WIDE_INT minsize;
 
   /* The access mode.  */
-  enum access_mode { none, read_only, write_only, read_write };
+  enum access_mode
+    {
+     none = 0,
+     read_only = 1,
+     write_only = 2,
+     read_write = read_only | write_only,
+     /* In an internal representation defers to the presence of
+	the const qualifier (treated as likely read_only) or to
+	an external/explicit specification of the attribute.  */
+     deferred
+    };
   access_mode mode;
+
+  /* Set for an attribute added internally rather than by an explicit
+     declaration. */
+  bool internal_p;
+  /* Set for the T[static MINSIZE] array notation for nonzero MINSIZE
+     less than HWI_M1U.  */
+  bool static_p;
+
+  /* Return the number of specified VLA bounds.  */
+  unsigned vla_bounds (unsigned *) const;
+
+  /* Return internal representation as STRING_CST.  */
+  tree to_internal_string () const;
+
+  /* Return the human-readable representation of the external attribute
+     specification (as it might appear in the source code) as STRING_CST.  */
+  tree to_external_string () const;
+
+  /* Return argument of array type formatted as a readable string.  */
+  std::string array_as_string (tree) const;
+
+  /* Return the access mode corresponding to the character code.  */
+  static access_mode from_mode_char (char);
+
+  /* The character codes corresponding to all the access modes.  */
+  static constexpr char mode_chars[5] = { '-', 'r', 'w', 'x', '^' };
+
+  /* The strings corresponding to just the external access modes.  */
+  static constexpr char mode_names[4][11] =
+    {
+     "none", "read_only", "write_only", "read_write"
+    };
 };
 
+inline attr_access::access_mode
+attr_access::from_mode_char (char c)
+{
+  switch (c)
+    {
+    case mode_chars[none]: return none;
+    case mode_chars[read_only]: return read_only;
+    case mode_chars[write_only]: return write_only;
+    case mode_chars[read_write]: return read_write;
+    case mode_chars[deferred]: return deferred;
+    }
+  gcc_unreachable ();
+}
+
 /* Used to define rdwr_map below.  */
 struct rdwr_access_hash: int_hash<int, -1> { };
 
@@ -247,5 +318,7 @@ struct attr_access;
 typedef hash_map<rdwr_access_hash, attr_access> rdwr_map;
 
 extern void init_attr_rdwr_indices (rdwr_map *, tree);
+extern attr_access *get_parm_access (rdwr_map &, tree,
+				     tree = current_function_decl);
 
 #endif // GCC_ATTRIBS_H
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index 37214831538..452600cbfc6 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License
 along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
+#define INCLUDE_STRING
 #include "config.h"
 #include "system.h"
 #include "coretypes.h"
@@ -45,6 +46,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "opts.h"
 #include "gimplify.h"
 #include "tree-pretty-print.h"
+#include "gcc-rich-location.h"
 
 static tree handle_packed_attribute (tree *, tree, tree, int, bool *);
 static tree handle_nocommon_attribute (tree *, tree, tree, int, bool *);
@@ -136,6 +138,7 @@ static tree handle_target_clones_attribute (tree *, tree, tree, int, bool *);
 static tree handle_optimize_attribute (tree *, tree, tree, int, bool *);
 static tree ignore_attribute (tree *, tree, tree, int, bool *);
 static tree handle_no_split_stack_attribute (tree *, tree, tree, int, bool *);
+static tree handle_argspec_attribute (tree *, tree, tree, int, bool *);
 static tree handle_fnspec_attribute (tree *, tree, tree, int, bool *);
 static tree handle_warn_unused_attribute (tree *, tree, tree, int, bool *);
 static tree handle_returns_nonnull_attribute (tree *, tree, tree, int, bool *);
@@ -434,6 +437,10 @@ const struct attribute_spec c_common_attribute_table[] =
 			      ignore_attribute, NULL },
   { "no_split_stack",	      0, 0, true,  false, false, false,
 			      handle_no_split_stack_attribute, NULL },
+  /* For internal use only (marking of function arguments).
+     The name contains a space to prevent its usage in source code.  */
+  { "arg spec",		      1, -1, true, false, false, false,
+			      handle_argspec_attribute, NULL },
   /* For internal use (marking of builtins and runtime functions) only.
      The name contains space to prevent its usage in source code.  */
   { "fn spec",		      1, 1, false, true, true, false,
@@ -3035,8 +3042,22 @@ handle_assume_aligned_attribute (tree *node, tree name, tree args, int,
   return NULL_TREE;
 }
 
-/* Handle a "fn spec" attribute; arguments as in
-   struct attribute_spec.handler.  */
+/* Handle the internal-only "arg spec" attribute.  */
+
+static tree
+handle_argspec_attribute (tree *, tree, tree args, int, bool *)
+{
+  /* Verify the attribute has one or two arguments and their kind.  */
+  gcc_assert (args && TREE_CODE (TREE_VALUE (args)) == STRING_CST);
+  for (tree next = TREE_CHAIN (args); next; next = TREE_CHAIN (next))
+    {
+      tree val = TREE_VALUE (next);
+      gcc_assert (DECL_P (val) || EXPR_P (val));
+    }
+  return NULL_TREE;
+}
+
+/* Handle the internal-only "fn spec" attribute.  */
 
 static tree
 handle_fnspec_attribute (tree *node ATTRIBUTE_UNUSED, tree ARG_UNUSED (name),
@@ -3822,7 +3843,8 @@ get_argument_type (tree functype, unsigned argno, unsigned *nargs)
 	  tree argtype = function_args_iter_cond (&iter);
 	  if (VOID_TYPE_P (argtype))
 	    break;
-	  return argtype;
+	  if (argtype != error_mark_node)
+	    return argtype;
 	}
     }
 
@@ -3830,143 +3852,271 @@ get_argument_type (tree functype, unsigned argno, unsigned *nargs)
   return NULL_TREE;
 }
 
-/* Appends ATTRSTR to the access string in ATTRS if one is there
-   or creates a new one and returns the concatenated access string.  */
+/* Given a function FNDECL return the function argument at the zero-
+   based position ARGNO or null if it can't be found.  */
 
 static tree
-append_access_attrs (tree t, tree attrs, const char *attrstr,
-		     char code, HOST_WIDE_INT idxs[2])
+get_argument (tree fndecl, unsigned argno)
 {
-  char attrspec[80];
-  int n1 = sprintf (attrspec, "%c%u", code, (unsigned) idxs[0] - 1);
-  int n2 = 0;
-  if (idxs[1])
-    n2 = sprintf (attrspec + n1 + 1, "%u", (unsigned) idxs[1] - 1);
+  if (!DECL_P (fndecl))
+    return NULL_TREE;
 
-  size_t newlen = n1 + n2 + !!n2;
-  char *newspec = attrspec;
+  unsigned i = 0;
+  for (tree arg = DECL_ARGUMENTS (fndecl); arg; arg = TREE_CHAIN (arg))
+    if (i++ == argno)
+      return arg;
 
-  if (tree acs = lookup_attribute ("access", attrs))
-    {
-      /* The TREE_VALUE of an attribute is a TREE_LIST whose TREE_VALUE
-	 is the attribute argument's value.  */
-      acs = TREE_VALUE (acs);
-      gcc_assert (TREE_CODE (acs) == TREE_LIST);
-      acs = TREE_VALUE (acs);
-      gcc_assert (TREE_CODE (acs) == STRING_CST);
+  return NULL_TREE;
+}
 
-      /* Check to make sure ATTRSPEC doesn't conflict with another
-	 access attribute specified in ATTRS by searching the access
-	 string in ATTRS for the position string formatted above into
-	 ATTRSPEC, and if it's found, that the two match.  */
+/* Attempt to append attribute access specification ATTRSPEC, optionally
+   described by the human-readable string ATTRSTR, for type T, to one in
+   ATTRS. VBLIST is an optional list of bounds of variable length array
+   parameters described by ATTRSTR.
+   Issue warning for conflicts and return null if any are found.
+   Return the concatenated access string on success.  */
 
-      const char *posstr = attrspec + 1;
-      const char *str = TREE_STRING_POINTER (acs);
-      const char *pos = str;
-      for ( ; ; pos += n1)
+static tree
+append_access_attr (tree node[3], tree attrs, const char *attrstr,
+		    const char *attrspec, tree vblist = NULL_TREE)
+{
+  tree argstr = build_string (strlen (attrspec) + 1, attrspec);
+  tree ataccess = tree_cons (NULL_TREE, argstr, vblist);
+  ataccess = tree_cons (get_identifier ("access"), ataccess, NULL_TREE);
+
+  /* The access specification being applied.  This may be an implicit
+     access spec synthesized for array (or VLA) parameters even for
+     a declaration with an explicit access spec already applied, if
+     this call corresponds to the first declaration of the function.  */
+  rdwr_map new_idxs;
+  init_attr_rdwr_indices (&new_idxs, ataccess);
+
+  /* The current access specification alrady applied.  */
+  rdwr_map cur_idxs;
+  init_attr_rdwr_indices (&cur_idxs, attrs);
+
+  std::string spec;
+  for (auto it = new_idxs.begin (); it != new_idxs.end (); ++it)
+    {
+      const auto &newaxsref = *it;
+
+      /* The map has two equal entries for each pointer argument that
+	 has an associated size argument.  Process just the entry for
+	 the former.  */
+      if ((unsigned)newaxsref.first != newaxsref.second.ptrarg)
+	continue;
+
+      const attr_access* const cura = cur_idxs.get (newaxsref.first);
+      if (!cura)
 	{
-	  pos = strstr (pos, posstr);
-	  if (!pos)
-	    break;
+	  /* The new attribute needs to be added.  */
+	  tree str = newaxsref.second.to_internal_string ();
+	  spec += TREE_STRING_POINTER (str);
+	  continue;
+	}
+
+      /* The new access spec refers to an array/pointer argument for
+	 which an access spec already exists.  Check and diagnose any
+	 conflicts.  If no conflicts are found, merge the two.  */
+      const attr_access* const newa = &newaxsref.second;
+
+      if (!attrstr)
+	{
+	  tree str = NULL_TREE;
+	  if (newa->mode != attr_access::deferred)
+	    str = newa->to_external_string ();
+	  else if (cura->mode != attr_access::deferred)
+	    str = cura->to_external_string ();
+	  if (str)
+	    attrstr = TREE_STRING_POINTER (str);
+	}
+
+      location_t curloc = input_location;
+      if (node[2] && DECL_P (node[2]))
+	curloc = DECL_SOURCE_LOCATION (node[2]);
+
+      location_t prevloc = UNKNOWN_LOCATION;
+      if (node[1] && DECL_P (node[1]))
+	prevloc = DECL_SOURCE_LOCATION (node[1]);
+
+      if (newa->mode != cura->mode
+	  && newa->mode != attr_access::deferred
+	  && cura->mode != attr_access::deferred
+	  && newa->internal_p == cura->internal_p)
+	{
+	  /* Mismatch in access mode.  */
+	  auto_diagnostic_group d;
+	  if (warning_at (curloc, OPT_Wattributes,
+			  "attribute %qs mismatch with mode %qs",
+			  attrstr, cura->mode_names[cura->mode])
+	      && prevloc != UNKNOWN_LOCATION)
+	    inform (prevloc, "previous declaration here");
+	  continue;
+	}
+
+      /* Set if PTRARG refers to a VLA with an unspecified bound (T[*]).
+	 Be prepared for either CURA or NEWA to refer to it, depending
+	 on which happens to come first in the declaration.  */
+      const bool cur_vla_ub = (cura->internal_p
+			       && cura->sizarg == UINT_MAX
+			       && cura->minsize == HOST_WIDE_INT_M1U);
+      const bool new_vla_ub = (newa->internal_p
+			       && newa->sizarg == UINT_MAX
+			       && newa->minsize == HOST_WIDE_INT_M1U);
+
+      if (newa->sizarg != cura->sizarg
+	  && attrstr
+	  && (!(cur_vla_ub ^ new_vla_ub)
+	      || (!cura->internal_p && !newa->internal_p)))
+	{
+	  /* Avoid diagnosing redeclarations of functions with no explicit
+	     attribute access that add one.  */
+	  if (newa->mode == attr_access::deferred
+	      && cura->mode != attr_access::deferred
+	      && newa->sizarg == UINT_MAX
+	      && cura->sizarg != UINT_MAX)
+	    continue;
 
-	  if (ISDIGIT (pos[-1]) || ISDIGIT (pos[n1 -1]))
+	  if (cura->mode == attr_access::deferred
+	      && newa->mode != attr_access::deferred
+	      && cura->sizarg == UINT_MAX
+	      && newa->sizarg != UINT_MAX)
 	    continue;
 
-	  /* Found a matching positional argument.  */
-	  if (*attrspec != pos[-1])
+	  /* The two specs designate different size arguments.  It's okay
+	     for the explicit spec to specify a size where none is provided
+	     by the implicit (VLA) one, as in:
+	       __attribute__ ((access (read_write, 1, 2)))
+	       void f (int*, int);
+	     but not for two explicit access attributes to do that.  */
+	  bool warned = false;
+
+	  auto_diagnostic_group d;
+
+	  if (newa->sizarg == UINT_MAX)
+	    /* Mismatch in the presence of the size argument.  */
+	    warned = warning_at (curloc, OPT_Wattributes,
+				 "attribute %qs missing positional argument 2 "
+				 "provided in previous designation by argument "
+				 "%u", attrstr, cura->sizarg + 1);
+	  else if (cura->sizarg == UINT_MAX)
+	    /* Mismatch in the presence of the size argument.  */
+	    warned = warning_at (curloc, OPT_Wattributes,
+				 "attribute %qs positional argument 2 "
+				 "missing in previous designation",
+				 attrstr);
+	  else if (newa->internal_p || cura->internal_p)
 	    {
-	      const char* const modestr
-		= (pos[-1] == 'r'
-		   ? "read_only"
-		   : (pos[-1] == 'w'
-		      ? "write_only"
-		      : (pos[-1] == 'x' ? "read_write" : "none")));
-	      /* Mismatch in access mode.  */
-	      auto_diagnostic_group d;
-	      if (warning (OPT_Wattributes,
-			   "attribute %qs mismatch with mode %qs",
-			   attrstr, modestr)
-		  && DECL_P (t))
-		inform (DECL_SOURCE_LOCATION (t),
-			"previous declaration here");
-	      return NULL_TREE;
+	      /* Mismatch in the value of the size argument and a VLA
+		 bound.  */
+	      location_t argloc = curloc;
+	      if (tree arg = get_argument (node[2], newa->sizarg))
+		argloc = DECL_SOURCE_LOCATION (arg);
+	      warned = warning_at (argloc, OPT_Wattributes,
+				   "attribute %qs positional argument 2 "
+				   "conflicts with previous designation "
+				   "by argument %u",
+				   attrstr, cura->sizarg + 1);
 	    }
-
-	  if ((n2 && pos[n1 - 1] != ','))
+	  else
+	    /* Mismatch in the value of the size argument between two
+	       explicit access attributes.  */
+	    warned = warning_at (curloc, OPT_Wattributes,
+				 "attribute %qs mismatched positional argument "
+				 "values %i and %i",
+				 attrstr, newa->sizarg + 1, cura->sizarg + 1);
+
+	  if (warned)
 	    {
-	      /* Mismatch in the presence of the size argument.  */
-	      auto_diagnostic_group d;
-	      if (warning (OPT_Wattributes,
-			   "attribute %qs positional argument 2 conflicts "
-			   "with previous designation",
-			   attrstr)
-		  && DECL_P (t))
-		inform (DECL_SOURCE_LOCATION (t),
-			"previous declaration here");
-	      return NULL_TREE;
-	    }
+	      /* If the previous declaration is a function (as opposed
+		 to a typedef of one), find the location of the array
+		 or pointer argument that uses the conflicting VLA bound
+		 and point to it in the note.  */
+	      const attr_access* const pa = cura->size ? cura : newa;
+	      tree size = pa->size ? TREE_VALUE (pa->size) : NULL_TREE;
+	      if (size && DECL_P (size))
+		{
+		  location_t argloc = UNKNOWN_LOCATION;
+		  if (tree arg = get_argument (node[2], pa->ptrarg))
+		    argloc = DECL_SOURCE_LOCATION (arg);
 
-	  if (!n2 && pos[n1 - 1] == ',')
-	    {
-	      /* Mismatch in the presence of the size argument.  */
-	      auto_diagnostic_group d;
-	      if (warning (OPT_Wattributes,
-			   "attribute %qs missing positional argument 2 "
-			   "provided in previous designation",
-			   attrstr)
-		  && DECL_P (t))
-		inform (DECL_SOURCE_LOCATION (t),
-			"previous declaration here");
-	      return NULL_TREE;
-	    }
+		  gcc_rich_location richloc (DECL_SOURCE_LOCATION (size));
+		  if (argloc != UNKNOWN_LOCATION)
+		    richloc.add_range (argloc);
 
-	  if (n2 && strncmp (attrspec + n1 + 1, pos + n1, n2))
-	    {
-	      /* Mismatch in the value of the size argument.  */
-	      auto_diagnostic_group d;
-	      if (warning (OPT_Wattributes,
-			   "attribute %qs mismatched positional argument "
-			   "values %i and %i",
-			   attrstr, atoi (attrspec + n1 + 1) + 1,
-			   atoi (pos + n1) + 1)
-		  && DECL_P (t))
-		inform (DECL_SOURCE_LOCATION (t),
-			"previous declaration here");
-	      return NULL_TREE;
+		  inform (&richloc, "designating the bound of variable "
+			  "length array argument %u",
+			  pa->ptrarg + 1);
+		}
+	      else if (prevloc != UNKNOWN_LOCATION)
+		inform (prevloc, "previous declaration here");
 	    }
 
-	  /* Avoid adding the same attribute specification.  */
-	  return NULL_TREE;
+	  continue;
 	}
 
-      /* Connect the two substrings formatted above into a single one.  */
-      if (idxs[1])
-	attrspec[n1] = ',';
+      if (newa->internal_p == cura->internal_p)
+	continue;
 
-      size_t len = strlen (str);
-      newspec = XNEWVEC (char, newlen + len + 1);
-      strcpy (newspec, str);
-      strcpy (newspec + len, attrspec);
-      newlen += len;
+      /* Merge the CURA and NEWA.  */
+      attr_access merged = newaxsref.second;
+
+      /* VLA seen in a declaration takes precedence.  */
+      if (cura->minsize == HOST_WIDE_INT_M1U)
+	merged.minsize = HOST_WIDE_INT_M1U;
+
+      /* Use the explicitly specified size positional argument.  */
+      if (cura->sizarg != UINT_MAX)
+	merged.sizarg = cura->sizarg;
+
+      /* Use the explicitly specified mode.  */
+      if (merged.mode == attr_access::deferred)
+	merged.mode = cura->mode;
+
+      tree str = merged.to_internal_string ();
+      spec += TREE_STRING_POINTER (str);
     }
-  else if (idxs[1])
-    /* Connect the two substrings formatted above into a single one.  */
-    attrspec[n1] = ',';
 
-  tree ret = build_string (newlen + 1, newspec);
-  if (newspec != attrspec)
-    XDELETEVEC (newspec);
-  return ret;
+  if (!spec.length ())
+    return NULL_TREE;
+
+  return build_string (spec.length (), spec.c_str ());
 }
 
-/* Handle the access attribute (read_only, write_only, and read_write).  */
+/* Convenience wrapper for the above.  */
+
+tree
+append_access_attr (tree node[3], tree attrs, const char *attrstr,
+		    char code, HOST_WIDE_INT idxs[2])
+{
+  char attrspec[80];
+  int n = sprintf (attrspec, "%c%u", code, (unsigned) idxs[0] - 1);
+  if (idxs[1])
+    n += sprintf (attrspec + n, ",%u", (unsigned) idxs[1] - 1);
+
+  return append_access_attr (node, attrs, attrstr, attrspec);
+}
+
+/* Handle the access attribute for function type NODE[0], with the function
+   DECL optionally in NODE[1].  The handler is called both in response to
+   an explict attribute access on a declaration with a mode and one or two
+   positional arguments, and for internally synthesized access specifications
+   with a string argument optionally followd by a DECL or expression
+   representing a VLA bound.  To speed up parsing, the handler transforms
+   the attribute and its arguments into a string.  */
 
 static tree
-handle_access_attribute (tree *node, tree name, tree args,
+handle_access_attribute (tree node[3], tree name, tree args,
 			 int ARG_UNUSED (flags), bool *no_add_attrs)
 {
+  tree attrs = TYPE_ATTRIBUTES (*node);
   tree type = *node;
-  tree attrs = TYPE_ATTRIBUTES (type);
+  if (POINTER_TYPE_P (type))
+    {
+      tree ptype = TREE_TYPE (type);
+      if (FUNC_OR_METHOD_TYPE_P (ptype))
+	type = ptype;
+    }
 
   *no_add_attrs = true;
 
@@ -3984,9 +4134,32 @@ handle_access_attribute (tree *node, tree name, tree args,
   tree access_mode = TREE_VALUE (args);
   if (TREE_CODE (access_mode) == STRING_CST)
     {
-      /* This must be a recursive call to handle the condensed internal
-	 form of the attribute (see below).  Since all validation has
-	 been done simply return here, accepting the attribute as is.  */
+      const char* const str = TREE_STRING_POINTER (access_mode);
+      if (*str == '+')
+	{
+	  /* This is a request to merge an internal specification for
+	     a function declaration involving arrays but no explicit
+	     attribute access.  */
+	  tree vblist = TREE_CHAIN (args);
+	  tree axstr = append_access_attr (node, attrs, NULL, str + 1,
+					   vblist);
+	  if (!axstr)
+	    return NULL_TREE;
+
+	  /* Replace any existing access attribute specification with
+	     the concatenation above.  */
+	  tree axsat = tree_cons (NULL_TREE, axstr, vblist);
+	  axsat = tree_cons (name, axsat, NULL_TREE);
+
+	  /* Recursively call self to "replace" the documented/external
+	     form of the attribute with the condensend internal form.  */
+	  decl_attributes (node, axsat, flags);
+	  return NULL_TREE;
+	}
+
+      /* This is a recursive call to handle the condensed internal form
+	 of the attribute (see below).  Since all validation has been
+	 done simply return here, accepting the attribute as is.  */
       *no_add_attrs = false;
       return NULL_TREE;
     }
@@ -4017,16 +4190,28 @@ handle_access_attribute (tree *node, tree name, tree args,
 	ps += 2;
     }
 
-  const bool read_only = !strncmp (ps, "read_only", 9);
-  const bool write_only = !strncmp (ps, "write_only", 10);
-  const bool read_write = !strncmp (ps, "read_write", 10);
-  if (!read_only && !write_only && !read_write && strncmp (ps, "none", 4))
-    {
-      error ("attribute %qE invalid mode %qs; expected one of "
-	     "%qs, %qs, %qs, or %qs", name, access_str,
-	     "read_only", "read_write", "write_only", "none");
-      return NULL_TREE;
-    }
+  int imode;
+
+  {
+    const int nmodes =
+      sizeof attr_access::mode_names / sizeof *attr_access::mode_names;
+
+    for (imode = 0; imode != nmodes; ++imode)
+      if (!strncmp (ps, attr_access::mode_names[imode],
+		    strlen (attr_access::mode_names[imode])))
+	break;
+
+    if (imode == nmodes)
+      {
+	error ("attribute %qE invalid mode %qs; expected one of "
+	       "%qs, %qs, %qs, or %qs", name, access_str,
+	       "read_only", "read_write", "write_only", "none");
+	return NULL_TREE;
+      }
+  }
+
+  const attr_access::access_mode mode =
+    static_cast<attr_access::access_mode>(imode);
 
   if (funcall)
     {
@@ -4149,7 +4334,7 @@ handle_access_attribute (tree *node, tree name, tree args,
       }
   }
 
-  if (read_write || write_only)
+  if (mode == attr_access::read_write || mode == attr_access::write_only)
     {
       /* Read_write and write_only modes must reference non-const
 	 arguments.  */
@@ -4182,35 +4367,164 @@ handle_access_attribute (tree *node, tree name, tree args,
   /* Verify that the new attribute doesn't conflict with any existing
      attributes specified on previous declarations of the same type
      and if not, concatenate the two.  */
-  const char code
-    = read_only ? 'r' : write_only ? 'w' : read_write ? 'x' : '-';
-  tree new_attrs = append_access_attrs (node[0], attrs, attrstr, code, idxs);
+  const char code = attr_access::mode_chars[mode];
+  tree new_attrs = append_access_attr (node, attrs, attrstr, code, idxs);
   if (!new_attrs)
     return NULL_TREE;
 
   /* Replace any existing access attribute specification with
      the concatenation above.  */
   new_attrs = tree_cons (NULL_TREE, new_attrs, NULL_TREE);
-  new_attrs = tree_cons (name, new_attrs, attrs);
+  new_attrs = tree_cons (name, new_attrs, NULL_TREE);
 
   if (node[1])
     {
       /* Repeat for the previously declared type.  */
       attrs = TYPE_ATTRIBUTES (TREE_TYPE (node[1]));
-      tree attrs1 = append_access_attrs (node[1], attrs, attrstr, code, idxs);
-      if (!attrs1)
+      new_attrs = append_access_attr (node, attrs, attrstr, code, idxs);
+      if (!new_attrs)
 	return NULL_TREE;
 
-      attrs1 = tree_cons (NULL_TREE, attrs1, NULL_TREE);
-      new_attrs = tree_cons (name, attrs1, attrs);
+      new_attrs = tree_cons (NULL_TREE, new_attrs, NULL_TREE);
+      new_attrs = tree_cons (name, new_attrs, NULL_TREE);
     }
 
   /* Recursively call self to "replace" the documented/external form
-     of the attribute with the condensend internal form.  */
+     of the attribute with the condensed internal form.  */
   decl_attributes (node, new_attrs, flags);
   return NULL_TREE;
 }
 
+/* Extract attribute "arg spec" from each FNDECL argument that has it,
+   build a single attribute access corresponding to all the arguments,
+   and return the result.  SKIP_VOIDPTR set to ignore void* parameters
+   (used for user-defined functions for which, unlike in for built-ins,
+   void* cannot be relied on to determine anything about the access
+   through it or whether it even takes place).
+
+   For example, the parameters in the declaration:
+
+     void f (int x, int y, char [x][1][y][3], char [y][2][y][5]);
+
+   result in the following attribute access:
+
+     value: "+^2[*],$0$1^3[*],$1$1"
+     chain: <0, x> <1, y>
+
+   where each <node> on the chain corresponds to one VLA bound for each
+   of the two parameters.  */
+
+tree
+build_attr_access_from_parms (tree parms, bool skip_voidptr)
+{
+  /* Maps each named integral argument DECL seen so far to its position
+     in the argument list; used to associate VLA sizes with arguments.  */
+  hash_map<tree, unsigned> arg2pos;
+
+  /* The string representation of the access specification for all
+     arguments.  */
+  std::string spec;
+  unsigned argpos = 0;
+
+  /* A TREE_LIST of VLA bounds.  */
+  tree vblist = NULL_TREE;
+
+  for (tree arg = parms; arg; arg = TREE_CHAIN (arg), ++argpos)
+    {
+      if (!DECL_P (arg))
+	continue;
+
+      tree argtype = TREE_TYPE (arg);
+      if (DECL_NAME (arg) && INTEGRAL_TYPE_P (argtype))
+	arg2pos.put (arg, argpos);
+
+      tree argspec = DECL_ATTRIBUTES (arg);
+      if (!argspec)
+	continue;
+
+      if (POINTER_TYPE_P (argtype))
+	{
+	  /* void* arguments in user-defined functions could point to
+	     anything; skip them.  */
+	  tree reftype = TREE_TYPE (argtype);
+	  if (skip_voidptr && VOID_TYPE_P (reftype))
+	    continue;
+	}
+
+      /* Each parameter should have at most one "arg spec" attribute.  */
+      argspec = lookup_attribute ("arg spec", argspec);
+      if (!argspec)
+	continue;
+
+      /* Attribute arg spec should have one or two arguments.  */
+      argspec = TREE_VALUE (argspec);
+
+      /* The attribute arg spec string.  */
+      tree str = TREE_VALUE (argspec);
+      const char *s = TREE_STRING_POINTER (str);
+
+      /* Create the attribute access string from the arg spec string,
+	 optionally followed by position of the VLA bound argument if
+	 it is one.  */
+      char specbuf[80];
+      int len = snprintf (specbuf, sizeof specbuf, "%c%u%s",
+			  attr_access::mode_chars[attr_access::deferred],
+			  argpos, s);
+      gcc_assert ((size_t) len < sizeof specbuf);
+
+      if (!spec.length ())
+	spec += '+';
+
+      spec += specbuf;
+
+      /* The (optional) list of expressions denoting the VLA bounds
+	 N in ARGTYPE <arg>[Ni]...[Nj]...[Nk].  */
+      tree argvbs = TREE_CHAIN (argspec);
+      if (argvbs)
+	{
+	  spec += ',';
+	  /* Add ARGVBS to the list.  Their presence is indicated by
+	     appending a comma followed by the dollar sign and, when
+	     it corresponds to a function parameter, the position of
+	     each bound Ni, so it can be distinguished from
+	     an unspecified bound (as in T[*]).  The list is in reverse
+	     order of arguments and needs to be reversed to access in
+	     order.  */
+	  vblist = tree_cons (NULL_TREE, argvbs, vblist);
+
+	  unsigned nelts = 0;
+	  for (tree vb = argvbs; vb; vb = TREE_CHAIN (vb), ++nelts)
+	    {
+	      tree bound = TREE_VALUE (vb);
+	      if (const unsigned *psizpos = arg2pos.get (bound))
+		{
+		  /* BOUND previously seen in the parameter list.  */
+		  TREE_PURPOSE (vb) = size_int (*psizpos);
+		  sprintf (specbuf, "$%u", *psizpos);
+		  spec += specbuf;
+		}
+	      else
+		{
+		  /* BOUND doesn't name a parameter (it could be a global
+		     variable or an expression such as a function call).  */
+		  spec += '$';
+		}
+	    }
+	}
+    }
+
+  if (!spec.length ())
+    return NULL_TREE;
+
+  /* Build a single attribute access with the string describing all
+     array arguments and an optional list of any non-parameter VLA
+     bounds in order.  */
+  tree str = build_string (spec.length (), spec.c_str ());
+  tree attrargs = tree_cons (NULL_TREE, str, vblist);
+  tree name = get_identifier ("access");
+  return tree_cons (name, attrargs, NULL_TREE);
+}
+
 /* Handle a "nothrow" attribute; arguments as in
    struct attribute_spec.handler.  */
 
diff --git a/gcc/c-family/c-warn.c b/gcc/c-family/c-warn.c
index c32d8228b5c..fea8885bf35 100644
--- a/gcc/c-family/c-warn.c
+++ b/gcc/c-family/c-warn.c
@@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License
 along with GCC; see the file COPYING3.  If not see
 <http://www.gnu.org/licenses/>.  */
 
+#define INCLUDE_STRING
 #include "config.h"
 #include "system.h"
 #include "coretypes.h"
@@ -37,6 +38,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "c-family/c-spellcheck.h"
 #include "calls.h"
 #include "stor-layout.h"
+#include "tree-pretty-print.h"
 
 /* Print a warning if a constant expression had overflow in folding.
    Invoke this function on every expression that the language
@@ -3099,3 +3101,340 @@ warn_for_address_or_pointer_of_packed_member (tree type, tree rhs)
 
   check_and_warn_address_or_pointer_of_packed_member (type, rhs);
 }
+
+/* Return EXPR + 1.  Convenience helper used below.  */
+
+static inline tree
+plus_one (tree expr)
+{
+  tree type = TREE_TYPE (expr);
+  return fold_build2 (PLUS_EXPR, type, expr, build_int_cst (type, 1));
+}
+
+/* Detect and diagnose a mismatch between an attribute access specification
+   on the original declaration of FNDECL and that on the parameters NEWPARMS
+   from its refeclaration.  ORIGLOC is the location of the first declaration
+   (FNDECL's is set to the location of the redeclaration).  */
+
+void
+warn_parm_array_mismatch (location_t origloc, tree fndecl, tree newparms)
+{
+    /* The original parameter list (copied from the original declaration
+       into the current [re]declaration, FNDECL)).  The two are equal if
+       and only if FNDECL is the first declaratation.  */
+  tree curparms = DECL_ARGUMENTS (fndecl);
+  if (!curparms || !newparms || curparms == newparms)
+    return;
+
+  if (TREE_CODE (curparms) != PARM_DECL
+      || TREE_CODE (newparms) != PARM_DECL)
+    return;
+  /* Extract the (possibly empty) attribute access specification from
+     the declaration and its type (it doesn't yet reflect those created
+     in response to NEWPARMS).  */
+  rdwr_map cur_idx;
+  tree fntype = TREE_TYPE (fndecl);
+  init_attr_rdwr_indices (&cur_idx, TYPE_ATTRIBUTES (fntype));
+
+  /* Build a (possibly null) chain of access attributes corresponding
+     to NEWPARMS.  */
+  const bool builtin = fndecl_built_in_p (fndecl);
+  tree newattrs = build_attr_access_from_parms (newparms, builtin);
+
+  /* Extract the (possibly empty) attribute access specification from
+     NEWATTRS.  */
+  rdwr_map new_idx;
+  init_attr_rdwr_indices (&new_idx, newattrs);
+
+  /* If both are empty there's nothing to do.  If at least one isn't
+     empty there may be mismatches, such as between f(T*) and f(T[1]),
+     where the former mapping woud be empty.  */
+  if (cur_idx.is_empty () && new_idx.is_empty ())
+    return;
+
+  /* Create an empty access specification and use it for pointers with
+     no spec of their own.  */
+  attr_access ptr_spec = { };
+
+  /* Iterate over the two lists of function parameters, comparing their
+     respective mappings and diagnosing mismatches.  */
+  unsigned parmpos = 0;
+  for (tree curp = curparms, newp = newparms; curp;
+       curp = TREE_CHAIN (curp), newp = TREE_CHAIN (newp), ++parmpos)
+    {
+      /* Only check pointers and C++ references.  */
+      tree newptype = TREE_TYPE (newp);
+      if (!POINTER_TYPE_P (newptype))
+	continue;
+
+      {
+	/* Skip mismatches in __builtin_va_list that is commonly
+	   an array but that in declarations of built-ins decays
+	   to a pointer.  */
+	if (builtin && TREE_TYPE (newptype) == TREE_TYPE (va_list_type_node))
+	  continue;
+      }
+
+      /* Access specs for the argument on the current (previous) and
+	 new (to replace the current) declarations.  Either may be null,
+	 indicating the parameter is an ordinary pointer with no size
+	 associated with it.  */
+      attr_access *cura = cur_idx.get (parmpos);
+      attr_access *newa = new_idx.get (parmpos);
+
+      if (!newa)
+	{
+	  /* Continue of both parameters are pointers with no size
+	     associated with it.  */
+	  if (!cura)
+	    continue;
+
+	  /* Otherwise point at PTR_SPEC and set its parameter pointer
+	     and number.  */
+	  newa = &ptr_spec;
+	  newa->ptr = newp;
+	  newa->ptrarg = parmpos;
+	}
+      else if (!cura)
+	{
+	  cura = &ptr_spec;
+	  cura->ptr = curp;
+	  cura->ptrarg = parmpos;
+	}
+
+      /* Set if the parameter is [re]declared as a VLA.  */
+      const bool cur_vla_p = cura->size || cura->minsize == HOST_WIDE_INT_M1U;
+      const bool new_vla_p = newa->size || newa->minsize == HOST_WIDE_INT_M1U;
+
+      if (DECL_P (curp))
+	origloc = DECL_SOURCE_LOCATION (curp);
+      else if (EXPR_P (curp) && EXPR_HAS_LOCATION (curp))
+	origloc = EXPR_LOCATION (curp);
+
+      /* The location of the parameter in the current redeclaration.  */
+      location_t newloc = DECL_SOURCE_LOCATION (newp);
+      if (origloc == UNKNOWN_LOCATION)
+	origloc = newloc;
+
+      tree curptype = TREE_TYPE (curp);
+      const std::string newparmstr = newa->array_as_string (newptype);
+      const std::string curparmstr = cura->array_as_string (curptype);
+      if (new_vla_p && !cur_vla_p)
+	{
+	  if (warning_at (newloc, OPT_Wvla_parameter,
+			  "argument %u of type %qs declared as "
+			  "a variable length array",
+			  parmpos + 1, newparmstr.c_str ()))
+	    inform (origloc,
+		    (cura == &ptr_spec
+		     ? G_("previously declared as a pointer %qs")
+		     : G_("previously declared as an ordinary array %qs")),
+		    curparmstr.c_str ());
+	  continue;
+	}
+
+      if (newa == &ptr_spec)
+	{
+	  /* The new declaration uses the pointer form.  Detect mismatches
+	     between the pointer and a previous array or VLA forms.  */
+	  if (cura->minsize == HOST_WIDE_INT_M1U)
+	    {
+	      /* Diagnose a pointer/VLA mismatch.  */
+	      if (warning_at (newloc, OPT_Wvla_parameter,
+			      "argument %u of type %qs declared "
+			      "as a pointer",
+			      parmpos + 1, newparmstr.c_str ()))
+		inform (origloc,
+			"previously declared as a variable length array %qs",
+			curparmstr.c_str ());
+	      continue;
+	    }
+
+	  if (cura->minsize && cura->minsize != HOST_WIDE_INT_M1U)
+	    {
+	      /* Diagnose mismatches between arrays with a constant
+		 bound and pointers.  */
+	      if (warning_at (newloc, OPT_Warray_parameter_,
+			      "argument %u of type %qs declared "
+			      "as a pointer",
+			      parmpos + 1, newparmstr.c_str ()))
+		inform (origloc, "previously declared as an array %qs",
+			curparmstr.c_str ());
+	      continue;
+	    }
+	}
+
+      if (!new_vla_p && cur_vla_p)
+	{
+	  if (warning_at (newloc, OPT_Wvla_parameter,
+			  "argument %u of type %qs declared "
+			  "as an ordinary array",
+			  parmpos + 1, newparmstr.c_str ()))
+	    inform (origloc,
+		    "previously declared as a variable length array %qs",
+		    curparmstr.c_str ());
+	  continue;
+	}
+
+      /* Move on to the next pair of parameters if both of the current
+	 pair are VLAs with a single variable bound that refers to
+	 a parameter at the same position.  */
+      if (newa->size && cura->size
+	  && newa->sizarg != UINT_MAX
+	  && newa->sizarg == cura->sizarg
+	  && newa->minsize == cura->minsize
+	  && !TREE_CHAIN (newa->size) && !TREE_CHAIN (cura->size))
+	continue;
+
+      if (newa->size || cura->size)
+	{
+	  unsigned newunspec, curunspec;
+	  unsigned newbnds = newa->vla_bounds (&newunspec) + newunspec;
+	  unsigned curbnds = cura->vla_bounds (&curunspec) + curunspec;
+
+	  if (newbnds != curbnds)
+	    {
+	      if (warning_n (newloc, OPT_Wvla_parameter, newbnds,
+			     "argument %u of type %qs declared with "
+			     "%u variable bound",
+			     "argument %u of type %qs declared with "
+			     "%u variable bounds",
+			     parmpos + 1, newparmstr.c_str (),
+			     newbnds))
+		inform_n (origloc, curbnds,
+			  "previously declared as %qs with %u variable bound",
+			  "previously declared as %qs with %u variable bounds",
+			  curparmstr.c_str (), curbnds);
+	      continue;
+	    }
+
+	  if (newunspec != curunspec)
+	    {
+	      if (warning_n (newloc, OPT_Wvla_parameter, newunspec,
+			     "argument %u of type %qs declared with "
+			     "%u unspecified variable bound",
+			     "argument %u of type %qs declared with "
+			     "%u unspecified variable bounds",
+			     parmpos + 1, newparmstr.c_str (), newunspec))
+		inform_n (origloc, curunspec,
+			  "previously declared as %qs with %u unspecified "
+			  "variable bound",
+			  "previously declared as %qs with %u unspecified "
+			  "variable bounds",
+			  curparmstr.c_str (), curunspec);
+	      continue;
+	    }
+	}
+
+      /* Iterate over the lists of VLA variable bounds, comparing each
+	 pair for equality, and diagnosing mismatches.  The case of
+	 the lists having different lengths is handled above so at
+	 this point they do .  */
+      for (tree newvbl = newa->size, curvbl = cura->size; newvbl;
+	   newvbl = TREE_CHAIN (newvbl), curvbl = TREE_CHAIN (curvbl))
+	{
+	  tree newpos = TREE_PURPOSE (newvbl);
+	  tree curpos = TREE_PURPOSE (curvbl);
+
+	  tree newbnd = TREE_VALUE (newvbl);
+	  tree curbnd = TREE_VALUE (curvbl);
+
+	  if (newpos == curpos && newbnd == curbnd)
+	    /* In the expected case when both bounds either refer to
+	       the same positional parameter or when neither does,
+	       and both are the same expression they are necessarily
+	       the same.  */
+	    continue;
+
+	  const char* const newbndstr =
+	    newbnd ? print_generic_expr_to_str (newbnd) : "*";
+	  const char* const curbndstr =
+	    curbnd ? print_generic_expr_to_str (curbnd) : "*";
+
+	  if (!newpos != !curpos
+	      || (newpos && !tree_int_cst_equal (newpos, curpos)))
+	    {
+	      /* Diagnose a mismatch between a specified VLA bound and
+		 an unspecified one.  This can only happen in the most
+		 significant bound.
+
+		 Distinguish between the common case of bounds that are
+		 other function parameters such as in
+		   f (int n, int[n]);
+		 and others.  */
+
+	      gcc_rich_location richloc (newloc);
+	      bool warned;
+	      if (newpos)
+		{
+		  /* Also underline the VLA bound argument.  */
+		  richloc.add_range (DECL_SOURCE_LOCATION (newbnd));
+		  warned = warning_at (&richloc, OPT_Wvla_parameter,
+				       "argument %u of type %qs declared "
+				       "with mismatched bound argument %E",
+				       parmpos + 1, newparmstr.c_str (),
+				       plus_one (newpos));
+		}
+	      else
+		warned = warning_at (&richloc, OPT_Wvla_parameter,
+				     "argument %u of type %qs declared "
+				     "with mismatched bound %<%s%>",
+				     parmpos + 1, newparmstr.c_str (),
+				     newbndstr);
+
+	      if (warned)
+		{
+		  gcc_rich_location richloc (origloc);
+		  if (curpos)
+		    {
+		      /* Also underline the VLA bound argument.  */
+		      richloc.add_range (DECL_SOURCE_LOCATION (curbnd));
+		      inform (&richloc, "previously declared as %qs with "
+			      "bound argument %E",
+			      curparmstr.c_str (), plus_one (curpos));
+		    }
+		  else
+		    inform (&richloc, "previously declared as %qs with bound "
+			    "%<%s%>", curparmstr.c_str (), curbndstr);
+
+		  continue;
+		}
+	    }
+
+	  if (!newpos && newbnd && curbnd)
+	    {
+	      /* The VLA bounds don't refer to other function parameters.
+		 Compare them lexicographically to detect gross mismatches
+		 such as between T[foo()] and T[bar()].  */
+	      if (operand_equal_p (newbnd, curbnd, OEP_LEXICOGRAPHIC))
+		continue;
+
+	      if (warning_at (newloc, OPT_Wvla_parameter,
+			      "argument %u of type %qs declared with "
+			      "mismatched bound %<%s%>",
+			      parmpos + 1, newparmstr.c_str (),
+			      newbndstr))
+		inform (origloc, "previously declared as %qs with bound %qs",
+			curparmstr.c_str (), curbndstr);
+	      continue;
+	    }
+	}
+
+      if (newa->minsize == cura->minsize
+	  || (((newa->minsize == 0 && newa->mode != attr_access::deferred)
+	       || (cura->minsize == 0 && cura->mode != attr_access::deferred))
+	      && newa != &ptr_spec
+	      && cura != &ptr_spec))
+	continue;
+
+      if (!newa->static_p && !cura->static_p && warn_array_parameter < 2)
+	/* Avoid warning about mismatches in ordinary (non-static) arrays
+	   at levels below 2.  */
+	continue;
+
+      if (warning_at (newloc, OPT_Warray_parameter_,
+		      "argument %u of type %qs with mismatched bound",
+		      parmpos + 1, newparmstr.c_str ()))
+	inform (origloc, "previously declared as %qs", curparmstr.c_str ());
+    }
+}
diff --git a/gcc/calls.c b/gcc/calls.c
index 72f89011584..400dd4fbc92 100644
--- a/gcc/calls.c
+++ b/gcc/calls.c
@@ -2210,13 +2210,13 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
 
   bitmap_obstack_release (NULL);
 
+  tree fntypeattrs = TYPE_ATTRIBUTES (fntype);
   /* Extract attribute alloc_size from the type of the called expression
      (which could be a function or a function pointer) 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 = lookup_attribute ("alloc_size",
-					  TYPE_ATTRIBUTES (fntype)))
+  if (tree alloc_size = lookup_attribute ("alloc_size", fntypeattrs))
     {
       tree args = TREE_VALUE (alloc_size);
       alloc_idx[0] = TREE_INT_CST_LOW (TREE_VALUE (args)) - 1;
@@ -2229,7 +2229,7 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
 
   /* Map of attribute accewss specifications for function arguments.  */
   rdwr_map rdwr_idx;
-  init_attr_rdwr_indices (&rdwr_idx, fntype);
+  init_attr_rdwr_indices (&rdwr_idx, fntypeattrs);
 
   /* I counts args in order (to be) pushed; ARGPOS counts in order written.  */
   for (argpos = 0; argpos < num_actuals; i--, argpos++)
diff --git a/gcc/testsuite/gcc.dg/attr-access-read-write-2.c b/gcc/testsuite/gcc.dg/attr-access-read-write-2.c
index c2ac6c344a5..deeee736eb8 100644
--- a/gcc/testsuite/gcc.dg/attr-access-read-write-2.c
+++ b/gcc/testsuite/gcc.dg/attr-access-read-write-2.c
@@ -22,12 +22,12 @@ int RW (1) grdwr1_wr1 (void*, void*);           // { dg-message "previous declar
 int WO (1) grdwr1_wr1 (void*, void*);         // { dg-warning "attribute 'access\\(write_only, 1\\)' mismatch with mode 'read_write'" }
 
 
-int RW (1) RW (1, 2) frdwr1_rdwr1_1 (void*, int);   // { dg-warning "attribute 'access\\(read_write, 1, 2\\)' positional argument 2 conflicts with previous designation" }
+int RW (1) RW (1, 2) frdwr1_rdwr1_1 (void*, int);   // { dg-warning "attribute 'access\\(read_write, 1, 2\\)' positional argument 2 missing in previous designation" }
 
 int RW (1, 2) RW (1) frdwr1_1_rdwr1 (void*, int);   // { dg-warning "attribute 'access\\(read_write, 1\\)' missing positional argument 2 provided in previous designation" }
 
 int RW (1)    grdwr1_rdwr1_1 (void*, int);   // { dg-message "previous declaration here" }
-int RW (1, 2) grdwr1_rdwr1_1 (void*, int);   // { dg-warning "attribute 'access\\(read_write, 1, 2\\)' positional argument 2 conflicts with previous designation" }
+int RW (1, 2) grdwr1_rdwr1_1 (void*, int);   // { dg-warning "attribute 'access\\(read_write, 1, 2\\)' positional argument 2 missing in previous designation" }
 
 
 typedef int *P;
diff --git a/gcc/tree-ssa-uninit.c b/gcc/tree-ssa-uninit.c
index 2f0ff724cde..a1ea1b6c532 100644
--- a/gcc/tree-ssa-uninit.c
+++ b/gcc/tree-ssa-uninit.c
@@ -472,7 +472,7 @@ maybe_warn_pass_by_reference (gimple *stmt, wlimits &wlims)
 
   /* Map of attribute access specifications for function arguments.  */
   rdwr_map rdwr_idx;
-  init_attr_rdwr_indices (&rdwr_idx, fntype);
+  init_attr_rdwr_indices (&rdwr_idx, TYPE_ATTRIBUTES (fntype));
 
   tree argtype;
   unsigned argno = 0;

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH 2/5] C front end support to detect out-of-bounds accesses to array parameters
  2020-08-07 17:01     ` Martin Sebor
@ 2020-08-12 23:19       ` Joseph Myers
  2020-08-13 23:04         ` Martin Sebor
  0 siblings, 1 reply; 33+ messages in thread
From: Joseph Myers @ 2020-08-12 23:19 UTC (permalink / raw)
  To: Martin Sebor; +Cc: gcc-patches

On Fri, 7 Aug 2020, Martin Sebor via Gcc-patches wrote:

> > I don't see anything in the tests in this patch to cover this sort of case
> > (arrays of pointers, including arrays of pointers to arrays etc.).
> 
> I've added a few test cases and reworked the declarator parsing
> (get_parm_array_spec) a bit, fixing some bugs.

I don't think get_parm_array_spec is yet logically right (and I don't see 
tests of the sort of cases I'm concerned about, such as arrays of pointers 
to arrays, or pointers with attributes applied to them).

You have logic

+      if (pd->kind == cdk_pointer
+         && (!next || next->kind == cdk_id))
+       {
+         /* Do nothing for the common case of a pointer.  The fact that
+            the parameter is one can be deduced from the absence of
+            an arg spec for it.  */
+         return attrs;
+       }

which is correct as far as it goes (when it returns with nothing done, 
it's correct to do so, because the argument is indeed a pointer), but 
incomplete:

* Maybe cdk_pointer is followed by cdk_attrs before cdk_id.  In this case 
the code won't return.

* Maybe the code is correct to continue because we're in the case of an 
array of pointers (cdk_array follows).  But as I understand it, the intent 
is to set up an "arg spec" that describes only the (multidimensional) 
array that is the parameter itself - not any array pointed to.  And it 
looks to me like, in the case of an array of pointers to arrays, both sets 
of array bounds would end up in the spec constructed.

What I think is correct is for both cdk_pointer and cdk_function to result 
in the spec built up so far being cleared (regardless of what follows 
cdk_pointer or cdk_function), rather than early return, so that the spec 
present at the end is for the innermost sequence of array declarators 
(possibly with attributes involved as well).  (cdk_function shouldn't 
actually be an issue, since functions can't return arrays or functions, 
but logically it seems appropriate to treat it like cdk_pointer.)

Then, the code

+      if (pd->kind == cdk_id)
+       {
+         /* Extract the upper bound from a parameter of an array type.  */

also seems misplaced.  If the type specifiers for the parameter are a 
typedef for an array type, that array type should be processed *before* 
the declarator to get the correct semantics (as if the bounds from those 
type specifiers were given in the declarator), not at the end which gets 
that type out of order with respect to array declarators.  (Processing 
before the declarator also means clearing the results of that processing 
if a pointer declarator is encountered at any point, because in that case 
the array type in the type specifiers is irrelevant.)

The logic

+         /* Skip all constant bounds except the most significant one.
+            The interior ones are included in the array type.  */
+         if (next && (next->kind == cdk_array || next->kind == cdk_pointer))
+           continue;

is another example of code that fails to look past cdk_attrs.

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH 0/5] add checking of function array parameters (PR 50584)
  2020-07-29  1:20 ` [PATCH 0/5] add checking of function array parameters (PR 50584) Martin Sebor
@ 2020-08-13 16:26   ` Jeff Law
  0 siblings, 0 replies; 33+ messages in thread
From: Jeff Law @ 2020-08-13 16:26 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches

On Tue, 2020-07-28 at 19:20 -0600, Martin Sebor via Gcc-patches wrote:
> Patch 3 adjusts tree-ssa-uninit.c to the changes to attribute access but
> has only a cosmetic effect on informational notes in -Wuninitialized.
> [3/5] - Make use of new attribute access infrastructure in tree-ssa-uninit.c.
> 
> gcc/ChangeLog:
> 
> 	* tree-ssa-uninit.c (maybe_warn_pass_by_reference): Handle attribute
> 	access internal representation of arrays.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* gcc.dg/uninit-37.c: New test.
> 
This is fine once the preqreqs are approved.

jeff


^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH 4/5] - extend -Wstringop-overflow to detect out-of-bounds accesses to array parameters
  2020-07-29  1:22 ` [PATCH 4/5] - extend -Wstringop-overflow to detect out-of-bounds accesses to array parameters Martin Sebor
@ 2020-08-13 16:31   ` Jeff Law
  0 siblings, 0 replies; 33+ messages in thread
From: Jeff Law @ 2020-08-13 16:31 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches

On Tue, 2020-07-28 at 19:22 -0600, Martin Sebor via Gcc-patches wrote:
> Patch 4 adds support to the machinery behind -Wstringop-overflow
> to issue warnings for (likely) out of bounds accesses in calls to
> functions with the internal attribute access specification.  This
> implements the feature pr50584 asks for (plus more).

> [4/5] - Extend -Wstringop-overflow to detect out-of-bounds accesses to array parameters.
> 
> gcc/ChangeLog:
> 
> 	* tree-ssa-uninit.c (maybe_warn_pass_by_reference): Handle attribute
> 	access internal representation of arrays.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* gcc.dg/uninit-37.c: New test.
> 
> gcc/ChangeLog:
> 
> 	PR c/50584
> 	* builtins.c (warn_for_access): Add argument.  Distinguish between
> 	reads and writes.
> 	(check_access): Add argument.  Distinguish between reads and writes.
> 	(gimple_call_alloc_size): Set range even on failure.
> 	(gimple_parm_array_size): New function.
> 	(compute_objsize): Call it.
> 	(check_memop_access): Pass check_access an additional argument.
> 	(expand_builtin_memchr, expand_builtin_strcat): Same.
> 	(expand_builtin_strcpy, expand_builtin_stpcpy_1): Same.
> 	(expand_builtin_stpncpy, check_strncat_sizes): Same.
> 	(expand_builtin_strncat, expand_builtin_strncpy): Same.
> 	(expand_builtin_memcmp): Same.
> 	* builtins.h (compute_objsize): Declare a new overload.
> 	(gimple_parm_array_size): Declare.
> 	(check_access): Add argument.
> 	* calls.c (append_attrname): Simplify.
> 	(maybe_warn_rdwr_sizes): Handle internal attribute access.
> 
> gcc/testsuite/ChangeLog:
> 
> 	PR c/50584
> 	* c-c++-common/Wsizeof-pointer-memaccess1.c: Disable new expected
> 	warnings.
> 	* g++.dg/ext/attr-access.C: Update text of expected warnings.
> 	* gcc.dg/Wstringop-overflow-23.c: Same.
> 	* gcc.dg/Wstringop-overflow-24.c: Same.
> 	* gcc.dg/attr-access-none.c: Same.
> 	* gcc.dg/Wstringop-overflow-40.c: New test.
> 	* gcc.dg/attr-access-2.c: New test.
OK once the prereqs are approved.

jeff
> 


^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH 5/5] extend -Warray-bounds to detect out-of-bounds accesses to array parameters
  2020-07-29  1:24 ` [PATCH 5/5] extend -Warray-bounds " Martin Sebor
@ 2020-08-13 16:49   ` Jeff Law
  0 siblings, 0 replies; 33+ messages in thread
From: Jeff Law @ 2020-08-13 16:49 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches

On Tue, 2020-07-28 at 19:24 -0600, Martin Sebor via Gcc-patches wrote:
> Patch 5 adds support for -Warray-bounds to detect out of bounds accesses
> in functions that take array/VLA arguments.  The changes also enable
> the warning for dynamically allocated memory and with it the detection
> of accesses that are only partially out of bounds (e.g., accessing
> a four byte int in the last two bytes of a buffer).  In hindsight this
> seems independent of the attribute access enhancement so I suppose it
> could have been split up into a separate change but I doubt it would
> reduce the size of the diff by more than 30 lines.

> [5/5] - Extend -Warray-bounds to detect out-of-bounds accesses to array parameters.
> 
> gcc/ChangeLog:
> 
> 	PR middle-end/82608
> 	PR middle-end/94195
> 	PR c/50584
> 	PR middle-end/84051
> 	* gimple-array-bounds.cc (get_base_decl): New function.
> 	(get_ref_size): New function.
> 	(trailing_array): New function.
> 	(array_bounds_checker::check_array_ref): Call them.  Handle arrays
> 	declared in function parameters.
> 	(array_bounds_checker::check_mem_ref):  Same.  Handle references to
> 	dynamically allocated arrays.
> 
> gcc/testsuite/ChangeLog:
> 
> 	PR middle-end/82608
> 	PR middle-end/94195
> 	PR c/50584
> 	PR middle-end/84051
> 	* gcc.dg/Warray-bounds-63.c: New test.
> 	* gcc.dg/Warray-bounds-64.c: New test.
> 	* gcc.dg/Warray-bounds-65.c: New test.
> 	* gcc.dg/Warray-bounds-66.c: New test.
> 
> diff --git a/gcc/gimple-array-bounds.cc b/gcc/gimple-array-bounds.cc
> index c2dd6663c3a..b93ef7a7b74 100644
> --- a/gcc/gimple-array-bounds.cc
> +++ b/gcc/gimple-array-bounds.cc
> @@ -36,6 +36,8 @@ along with GCC; see the file COPYING3.  If not see
>  #include "vr-values.h"
>  #include "domwalk.h"
>  #include "tree-cfg.h"
> +#include "attribs.h"
> +#include "builtins.h"
>  
>  // This purposely returns a value_range, not a value_range_equiv, to
>  // break the dependency on equivalences for this pass.
> @@ -46,19 +48,137 @@ array_bounds_checker::get_value_range (const_tree op)
>    return ranges->get_value_range (op);
>  }
>  
> +/* Try to determine the DECL that REF refers to.  Return the DECL or
> +   the expression closest to it.  Used in informational notes pointing
> +   to referenced objects or function parameters.  */
> +
> +static tree
> +get_base_decl (tree ref)
[ ... ]

> +
> +/* Return the constant byte size of the object or type referenced by
> +   the MEM_REF ARG.  On success, set *PREF to the DECL or expression
> +   ARG refers to.  Otherwise return null.  */
> +
> +static tree
> +get_ref_size (tree arg, tree *pref)
[ ... ]
I'm surprised we don't already have routines to do this.  
get_ref_base_and_extent perhaps?

Otherwise it seems reasonable to me.  
Jeff


> 


^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH 1/5] infrastructure to detect out-of-bounds accesses to array parameters
  2020-08-07 17:08   ` Martin Sebor
@ 2020-08-13 19:09     ` Jeff Law
  0 siblings, 0 replies; 33+ messages in thread
From: Jeff Law @ 2020-08-13 19:09 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches

On Fri, 2020-08-07 at 11:08 -0600, Martin Sebor via Gcc-patches wrote:
> On 7/28/20 7:16 PM, Martin Sebor wrote:
> > Patch 1 adds the basic infrastructure to support array/VLA bounds
> > in attribute access.  It extends the access string specification
> > to describe function parameters of array types (including VLAs),
> > extends the attr_access class to parse the string and store
> > the data in a form that's easy to work with, and implements
> > checking of various kinds f mismatches between redeclarations.
> > It doesn't actually enable anything new so no new tests are added.
> 
> Joseph's comments on patch 2 in the series prompted me to change
> how the array (and VLA) function parameters are formatted: instead
> of implementing it mostly outside the pretty printer (which, to do
> completely correctly, would require reimplementing what the pretty
> printer already does) I instead enhanced the pretty printer.  That
> let me simplify the formatting done in the helper.  The attached
> revision reflects this simplification (the only change from
> the original is to attr_access::array_as_string).
> 
> Martin
> [1/5] - Infrastructure to detect out-of-bounds accesses to array parameters.
> 
> gcc/ChangeLog:
> 
> 	PR c/50584
> 	* attribs.c (decl_attributes): Also pass decl along with type
> 	attributes to handlers.
> 	(init_attr_rdwr_indices): Change second argument to attribute chain.
> 	Handle internal attribute representation in addition to external.
> 	(get_parm_access): New function.
> 	(attr_access::to_internal_string): Define new member function.
> 	(attr_access::to_external_string): Define new member function.
> 	(attr_access::vla_bounds): Define new member function.
> 	* attribs.h (struct attr_access): Declare new members.
> 	(attr_access::from_mode_char): Define new member function.
> 	(get_parm_access): Declare new function.
> 	* calls.c (initialize_argument_information): Pass function type
> 	attributes to init_attr_rdwr_indices.
> 	* tree-ssa-uninit.c (maybe_warn_pass_by_reference): Same.
> 
> gcc/c-family/ChangeLog:
> 
> 	PR c/50584
> 	* c-attribs.c (c_common_attribute_table): Add "arg spec" attribute.
> 	(handle_argspec_attribute): New function.
> 	(get_argument, get_argument_type): New functions.
> 	(append_access_attrs): Add overload.  Handle internal attribute
> 	representation in addition to external.
> 	(handle_access_attribute): Handle internal attribute representation
> 	in addition to external.
> 	(build_attr_access_from_parms): New function.
> 	* c-warn.c (parm_array_as_string): Define new function.
> 	(plus_one):  Define new function.
> 	(warn_parm_array_mismatch): Define new function.
> 
> gcc/testsuite/ChangeLog:
> 
> 	PR c/50584
> 	* gcc.dg/attr-access-read-write-2.c: Adjust text of expected messages.
LGTM.
jeff
> 


^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH 2/5] C front end support to detect out-of-bounds accesses to array parameters
  2020-08-12 23:19       ` Joseph Myers
@ 2020-08-13 23:04         ` Martin Sebor
  2020-08-17 22:09           ` Joseph Myers
  0 siblings, 1 reply; 33+ messages in thread
From: Martin Sebor @ 2020-08-13 23:04 UTC (permalink / raw)
  To: Joseph Myers; +Cc: gcc-patches

[-- Attachment #1: Type: text/plain, Size: 4791 bytes --]

On 8/12/20 5:19 PM, Joseph Myers wrote:
> On Fri, 7 Aug 2020, Martin Sebor via Gcc-patches wrote:
> 
>>> I don't see anything in the tests in this patch to cover this sort of case
>>> (arrays of pointers, including arrays of pointers to arrays etc.).
>>
>> I've added a few test cases and reworked the declarator parsing
>> (get_parm_array_spec) a bit, fixing some bugs.
> 
> I don't think get_parm_array_spec is yet logically right (and I don't see
> tests of the sort of cases I'm concerned about, such as arrays of pointers
> to arrays, or pointers with attributes applied to them).

Please have a look at the tests at the end of Warrary-parameter-4.c.
They should exercise the cases you brought up (as I understand them).
There are some tests for attributes in Warray-parameter.c.

> You have logic
> 
> +      if (pd->kind == cdk_pointer
> +         && (!next || next->kind == cdk_id))
> +       {
> +         /* Do nothing for the common case of a pointer.  The fact that
> +            the parameter is one can be deduced from the absence of
> +            an arg spec for it.  */
> +         return attrs;
> +       }
> 
> which is correct as far as it goes (when it returns with nothing done,
> it's correct to do so, because the argument is indeed a pointer), but
> incomplete:
> 
> * Maybe cdk_pointer is followed by cdk_attrs before cdk_id.  In this case
> the code won't return.

I think I see the problem you're pointing out (I just don't see how
to trigger it or test that it doesn't happen).  If the tweak in
the attached update doesn't fix it a test case would be helpful.

> 
> * Maybe the code is correct to continue because we're in the case of an
> array of pointers (cdk_array follows).  But as I understand it, the intent
> is to set up an "arg spec" that describes only the (multidimensional)
> array that is the parameter itself - not any array pointed to.  And it
> looks to me like, in the case of an array of pointers to arrays, both sets
> of array bounds would end up in the spec constructed.

Ideally, I'd like to check even pointers to arrays and so they should
be recorded somewhere.  The middle end code doesn't do any checking
of those yet for out-of-bounds accesses.  It wasn't a goal for
the first iteration so I've tweaked the code to avoid recording them.

> What I think is correct is for both cdk_pointer and cdk_function to result
> in the spec built up so far being cleared (regardless of what follows
> cdk_pointer or cdk_function), rather than early return, so that the spec
> present at the end is for the innermost sequence of array declarators
> (possibly with attributes involved as well).  (cdk_function shouldn't
> actually be an issue, since functions can't return arrays or functions,
> but logically it seems appropriate to treat it like cdk_pointer.)

I've added a test for cdk_function to the one for cdk_pointer.

> 
> Then, the code
> 
> +      if (pd->kind == cdk_id)
> +       {
> +         /* Extract the upper bound from a parameter of an array type.  */
> 
> also seems misplaced.  If the type specifiers for the parameter are a
> typedef for an array type, that array type should be processed *before*
> the declarator to get the correct semantics (as if the bounds from those
> type specifiers were given in the declarator), not at the end which gets
> that type out of order with respect to array declarators.  (Processing
> before the declarator also means clearing the results of that processing
> if a pointer declarator is encountered at any point, because in that case
> the array type in the type specifiers is irrelevant.)

I'm not sure I follow you here.  Can you show me what you mean on
a piece of code?  This test case (which IIUC does what you described)
works as expected:

$ cat q.c && gcc -O2 -S -Wall q.c
typedef int A[7][9];

void f (A[3][5]);
void f (A[1][5]);

void g (void)
{
   A a[2][5];
   f (a);
}
q.c:4:9: warning: argument 1 of type ‘int[1][5][7][9]’ with mismatched 
bound [-Warray-parameter=]
     4 | void f (A[1][5]);
       |         ^~~~~~~
q.c:3:9: note: previously declared as ‘int[3][5][7][9]’
     3 | void f (A[3][5]);
       |         ^~~~~~~
q.c: In function ‘g’:
q.c:9:3: warning: ‘f’ accessing 3780 bytes in a region of size 2520 
[-Wstringop-overflow=]
     9 |   f (a);
       |   ^~~~~
q.c:9:3: note: referencing argument 1 of type ‘int (*)[5][7][9]’

> 
> The logic
> 
> +         /* Skip all constant bounds except the most significant one.
> +            The interior ones are included in the array type.  */
> +         if (next && (next->kind == cdk_array || next->kind == cdk_pointer))
> +           continue;
> 
> is another example of code that fails to look past cdk_attrs.

It should be handled by the tweak I added in the attached revision.

Martin


[-- Attachment #2: gcc-50584-2.diff --]
[-- Type: text/x-patch, Size: 59645 bytes --]

[2/5] - C front end support to detect out-of-bounds accesses to array parameters.

gcc/c-family/ChangeLog:

	PR c/50584
	* c-common.h (warn_parm_array_mismatch): Declare new function.
	(has_attribute): Move declaration of an existing function.
	(build_attr_access_from_parms): Declare new function.
	* c.opt (-Warray-parameter, -Wvla-parameter): New options.
	* c-pretty-print.c (pp_c_type_qualifier_list): Don't print array type
	qualifiers here...
	(c_pretty_printer::direct_abstract_declarator): ...but instead print
	them in brackets here.  Also print [static].  Strip extraneous
	expressions from VLA bounds.

gcc/c/ChangeLog:

	PR c/50584
	* c-decl.c (lookup_last_decl): Define new function.
	(c_decl_attributes): Call it.
	(start_decl): Add argument and use it.
	(finish_decl): Call build_attr_access_from_parms and decl_attributes.
	(get_parm_array_spec): Define new function.
	(push_parm_decl): Call get_parm_array_spec.
	(start_function): Call warn_parm_array_mismatch.  Build attribute
	access and add it to current function.
	* c-parser.c (c_parser_declaration_or_fndef): Diagnose mismatches
	in forms of array parameters.
	* c-tree.h (start_decl): Add argument.

gcc/ChangeLog:

	PR c/50584
	* calls.c (initialize_argument_information): Remove assertion.
	* doc/invoke.texi (-Warray-parameter, -Wvla-parameter): Document.
	* tree-pretty-print.c (dump_generic_node): Correct handling of
	qualifiers.

gcc/testsuite/ChangeLog:

	PR c/50584
	* c-c++-common/Warray-bounds-6.c: Correct C++ declaration, adjust
	text of expected diagnostics.
	* gcc.dg/Wbuiltin-declaration-mismatch-9.c: Prune expected warning.
	* gcc.dg/Warray-parameter-2.c: New test.
	* gcc.dg/Warray-parameter-3.c: New test.
	* gcc.dg/Warray-parameter-4.c: New test.
	* gcc.dg/Warray-parameter.c: New test.
	* gcc.dg/Wvla-parameter-2.c: New test.
	* gcc.dg/Wvla-parameter.c: New test.

diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index 4fc64bc4aa6..a61b25f95f1 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -1321,6 +1321,7 @@ extern void c_do_switch_warnings (splay_tree, location_t, tree, tree, bool);
 extern void warn_for_omitted_condop (location_t, tree);
 extern bool warn_for_restrict (unsigned, tree *, unsigned);
 extern void warn_for_address_or_pointer_of_packed_member (tree, tree);
+extern void warn_parm_array_mismatch (location_t, tree, tree);
 
 /* Places where an lvalue, or modifiable lvalue, may be required.
    Used to select diagnostic messages in lvalue_error and
@@ -1375,6 +1376,8 @@ extern tree find_tm_attribute (tree);
 extern const struct attribute_spec::exclusions attr_cold_hot_exclusions[];
 extern const struct attribute_spec::exclusions attr_noreturn_exclusions[];
 extern tree handle_noreturn_attribute (tree *, tree, tree, int, bool *);
+extern bool has_attribute (location_t, tree, tree, tree (*)(tree));
+extern tree build_attr_access_from_parms (tree, bool);
 
 /* In c-format.c.  */
 extern bool valid_format_string_type_p (tree);
@@ -1403,8 +1406,6 @@ extern void maybe_suggest_missing_token_insertion (rich_location *richloc,
 						   location_t prev_token_loc);
 extern tree braced_lists_to_strings (tree, tree);
 
-extern bool has_attribute (location_t, tree, tree, tree (*)(tree));
-
 #if CHECKING_P
 namespace selftest {
   /* Declarations for specific families of tests within c-family,
diff --git a/gcc/c-family/c-pretty-print.c b/gcc/c-family/c-pretty-print.c
index ec0bafe1010..03c89385386 100644
--- a/gcc/c-family/c-pretty-print.c
+++ b/gcc/c-family/c-pretty-print.c
@@ -248,9 +248,12 @@ pp_c_type_qualifier_list (c_pretty_printer *pp, tree t)
   if (!TYPE_P (t))
     t = TREE_TYPE (t);
 
-  qualifiers = TYPE_QUALS (t);
-  pp_c_cv_qualifiers (pp, qualifiers,
-		      TREE_CODE (t) == FUNCTION_TYPE);
+  if (TREE_CODE (t) != ARRAY_TYPE)
+    {
+      qualifiers = TYPE_QUALS (t);
+      pp_c_cv_qualifiers (pp, qualifiers,
+			  TREE_CODE (t) == FUNCTION_TYPE);
+    }
 
   if (!ADDR_SPACE_GENERIC_P (TYPE_ADDR_SPACE (t)))
     {
@@ -572,6 +575,8 @@ c_pretty_printer::abstract_declarator (tree t)
 void
 c_pretty_printer::direct_abstract_declarator (tree t)
 {
+  bool add_space = false;
+
   switch (TREE_CODE (t))
     {
     case POINTER_TYPE:
@@ -585,17 +590,65 @@ c_pretty_printer::direct_abstract_declarator (tree t)
 
     case ARRAY_TYPE:
       pp_c_left_bracket (this);
+
+      if (int quals = TYPE_QUALS (t))
+	{
+	  /* Print the array qualifiers such as in "T[const restrict 3]".  */
+	  pp_c_cv_qualifiers (this, quals, false);
+	  add_space = true;
+	}
+
+      if (tree arr = lookup_attribute ("array", TYPE_ATTRIBUTES (t)))
+	{
+	  if (TREE_VALUE (arr))
+	    {
+	      /* Print the specifier as in "T[static 3]" that's not actually
+		 part of the type but may be added by the front end.  */
+	      pp_c_ws_string (this, "static");
+	      add_space = true;
+	    }
+	  else if (!TYPE_DOMAIN (t))
+	    /* For arrays of unspecified bound using the [*] notation. */
+	    pp_character (this, '*');
+	}
+
       if (tree dom = TYPE_DOMAIN (t))
 	{
 	  if (tree maxval = TYPE_MAX_VALUE (dom))
 	    {
+	      if (add_space)
+		pp_space (this);
+
 	      tree type = TREE_TYPE (maxval);
 
 	      if (tree_fits_shwi_p (maxval))
 		pp_wide_integer (this, tree_to_shwi (maxval) + 1);
-	      else
+	      else if (TREE_CODE (maxval) == INTEGER_CST)
 		expression (fold_build2 (PLUS_EXPR, type, maxval,
 					 build_int_cst (type, 1)));
+	      else
+		{
+		  /* Strip the expressions from around a VLA bound added
+		     internally to make it fit the domain mold, including
+		     any casts.  */
+		  if (TREE_CODE (maxval) == NOP_EXPR)
+		    maxval = TREE_OPERAND (maxval, 0);
+		  if (TREE_CODE (maxval) == PLUS_EXPR
+		      && integer_all_onesp (TREE_OPERAND (maxval, 1)))
+		    {
+		      maxval = TREE_OPERAND (maxval, 0);
+		      if (TREE_CODE (maxval) == NOP_EXPR)
+			maxval = TREE_OPERAND (maxval, 0);
+		    }
+		  if (TREE_CODE (maxval) == SAVE_EXPR)
+		    {
+		      maxval = TREE_OPERAND (maxval, 0);
+		      if (TREE_CODE (maxval) == NOP_EXPR)
+			maxval = TREE_OPERAND (maxval, 0);
+		    }
+
+		  expression (maxval);
+		}
 	    }
 	  else if (TYPE_SIZE (t))
 	    /* Print zero for zero-length arrays but not for flexible
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 2b1aca16eb4..6f3997405a1 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -338,6 +338,14 @@ Warray-bounds=
 LangEnabledBy(C ObjC C++ LTO ObjC++,Wall,1,0)
 ; in common.opt
 
+Warray-parameter
+C ObjC C++ ObjC++ Warning Alias(Warray-parameter=, 2, 0)
+Warn about mismatched declarations of array parameters and unsafe accesses to them.
+
+Warray-parameter=
+C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_array_parameter) IntegerRange(0, 2) LangEnabledBy(C ObjC C++ ObjC++,Wall, 2, 0) Warning
+Warn about mismatched declarations of array parameters and unsafe accesses to them.
+
 Wzero-length-bounds
 C ObjC C++ ObjC++ Var(warn_zero_length_bounds) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall)
 Warn about accesses to interior zero-length array members.
@@ -1253,6 +1261,10 @@ Wno-vla-larger-than
 C ObjC C++ LTO ObjC++ Alias(Wvla-larger-than=,18446744073709551615EiB,none) Warning
 Disable Wvla-larger-than= warning.  Equivalent to Wvla-larger-than=<SIZE_MAX> or larger.
 
+Wvla-parameter
+C ObjC C++ ObjC++ Var(warn_vla_parameter) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall)
+Warn about mismatched declarations of VLA parameters.
+
 Wvolatile
 C++ ObjC++ Var(warn_volatile) Warning
 Warn about deprecated uses of volatile qualifier.
diff --git a/gcc/c/c-decl.c b/gcc/c/c-decl.c
index 5d6b504fe78..13f7dc29ce9 100644
--- a/gcc/c/c-decl.c
+++ b/gcc/c/c-decl.c
@@ -25,6 +25,7 @@ along with GCC; see the file COPYING3.  If not see
    line numbers.  For example, the CONST_DECLs for enum values.  */
 
 #include "config.h"
+#define INCLUDE_STRING
 #define INCLUDE_UNIQUE_PTR
 #include "system.h"
 #include "coretypes.h"
@@ -58,6 +59,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "c-family/known-headers.h"
 #include "c-family/c-spellcheck.h"
 
+#include "tree-pretty-print.h"
+
 /* In grokdeclarator, distinguish syntactic contexts of declarators.  */
 enum decl_context
 { NORMAL,			/* Ordinary declaration */
@@ -4967,6 +4970,17 @@ groktypename (struct c_type_name *type_name, tree *expr,
   return type;
 }
 
+/* Looks up the most recent pushed declaration corresponding to DECL.  */
+
+static tree
+lookup_last_decl (tree decl)
+{
+  tree last_decl = lookup_name (DECL_NAME (decl));
+  if (!last_decl)
+    last_decl = lookup_name_in_scope (DECL_NAME (decl), external_scope);
+  return last_decl;
+}
+
 /* Wrapper for decl_attributes that adds some implicit attributes
    to VAR_DECLs or FUNCTION_DECLs.  */
 
@@ -4995,10 +5009,7 @@ c_decl_attributes (tree *node, tree attributes, int flags)
      so far so that attributes on the current declaration that's
      about to be pushed that conflict with the former can be detected,
      diagnosed, and rejected as appropriate.  */
-  tree last_decl = lookup_name (DECL_NAME (*node));
-  if (!last_decl)
-    last_decl = lookup_name_in_scope (DECL_NAME (*node), external_scope);
-
+  tree last_decl = lookup_last_decl (*node);
   return decl_attributes (node, attributes, flags, last_decl);
 }
 
@@ -5008,6 +5019,8 @@ c_decl_attributes (tree *node, tree attributes, int flags)
    have been parsed, before parsing the initializer if any.
    Here we create the ..._DECL node, fill in its type,
    and put it on the list of decls for the current context.
+   When nonnull, set *LASTLOC to the location of the prior declaration
+   of the same entity if one exists.
    The ..._DECL node is returned as the value.
 
    Exception: for arrays where the length is not specified,
@@ -5020,7 +5033,7 @@ c_decl_attributes (tree *node, tree attributes, int flags)
 
 tree
 start_decl (struct c_declarator *declarator, struct c_declspecs *declspecs,
-	    bool initialized, tree attributes)
+	    bool initialized, tree attributes, location_t *lastloc /* = NULL */)
 {
   tree decl;
   tree tem;
@@ -5038,6 +5051,10 @@ start_decl (struct c_declarator *declarator, struct c_declspecs *declspecs,
   if (!decl || decl == error_mark_node)
     return NULL_TREE;
 
+  if (tree lastdecl = lastloc ? lookup_last_decl (decl) : NULL_TREE)
+    if (lastdecl != error_mark_node)
+      *lastloc = DECL_SOURCE_LOCATION (lastdecl);
+
   if (expr)
     add_stmt (fold_convert (void_type_node, expr));
 
@@ -5475,6 +5492,14 @@ finish_decl (tree decl, location_t init_loc, tree init,
 	  if (asmspec && VAR_P (decl) && C_DECL_REGISTER (decl))
 	    DECL_HARD_REGISTER (decl) = 1;
 	  rest_of_decl_compilation (decl, true, 0);
+
+	  if (TREE_CODE (decl) == FUNCTION_DECL)
+	    {
+	      tree parms = DECL_ARGUMENTS (decl);
+	      const bool builtin = fndecl_built_in_p (decl);
+	      if (tree access = build_attr_access_from_parms (parms, !builtin))
+		decl_attributes (&decl, access, 0);
+	    }
 	}
       else
 	{
@@ -5627,6 +5652,117 @@ grokparm (const struct c_parm *parm, tree *expr)
   return decl;
 }
 
+/* Return attribute "arg spec" corresponding to an array/VLA parameter
+   described by PARM, concatenated onto attributes ATTRS.  No "arg spec"
+   is created for parameters of pointer types, making a distinction
+   between T(*)[N] (or, equivalently, T[][N]) and the T[M][N] form, all
+   of which have the same type and are represented the same, but only
+   the last of which gets an "arg spec" describing the most significant
+   bound M.  */
+
+static tree
+get_parm_array_spec (const struct c_parm *parm, tree attrs)
+{
+  std::string spec;
+
+  /* A list of VLA variable bounds or null if not specified.  */
+  tree vbchain = NULL_TREE;
+
+  /* Create a string representation for the bounds of the array/VLA.  */
+  for (c_declarator *pd = parm->declarator, *next; pd; pd = next)
+    {
+      next = pd->declarator;
+      if (next && next->kind == cdk_attrs)
+	next = next->declarator;
+
+      if ((pd->kind == cdk_pointer || pd->kind == cdk_function)
+	  && (!next || next->kind == cdk_id))
+	{
+	  /* Do nothing for the common case of a pointer.  The fact that
+	     the parameter is one can be deduced from the absence of
+	     an arg spec for it.  */
+	  return attrs;
+	}
+
+      if (pd->kind == cdk_id)
+	{
+	  /* Extract the upper bound from a parameter of an array type.  */
+	  if (parm->specs->type
+	      && TREE_CODE (parm->specs->type) == ARRAY_TYPE
+	      && TYPE_DOMAIN (parm->specs->type)
+	      && TYPE_MAX_VALUE (TYPE_DOMAIN (parm->specs->type)))
+	    {
+	      tree max = TYPE_MAX_VALUE (TYPE_DOMAIN (parm->specs->type));
+	      if (tree_fits_shwi_p (max))
+		{
+		  /* The upper bound is the value of the largest valid index.  */
+		  HOST_WIDE_INT n = tree_to_shwi (max) + 1;
+		  char buf[40];
+		  sprintf (buf, "%lu", (unsigned long)n);
+		  spec += buf;
+		}
+	    }
+	  break;
+	}
+
+      if (pd->kind != cdk_array)
+	continue;
+
+      if (pd->u.array.vla_unspec_p)
+	{
+	  /* Each unspecified bound is represented by a star.  There
+	     can be any number of these in a declaration (but none in
+	     a definition).  */
+	  spec += '*';
+	  continue;
+	}
+
+      tree nelts = pd->u.array.dimen;
+      if (!nelts)
+	{
+	  /* Ordinary array of unspecified size is represented by
+	     a space.  There can be at most one for the most significant
+	     bound.  */
+	  spec += ' ';
+	  break;
+	}
+
+      if (TREE_CODE (nelts) == INTEGER_CST)
+	{
+	  /* Skip all constant bounds except the most significant one.
+	     The interior ones are included in the array type.  */
+	  if (next && (next->kind == cdk_array || next->kind == cdk_pointer))
+	    continue;
+
+	  if (!tree_fits_uhwi_p (nelts))
+	    /* Bail completely on invalid bounds.  */
+	    return attrs;
+
+	  char buf[40];
+	  const char *code = pd->u.array.static_p ? "s" : "";
+	  unsigned HOST_WIDE_INT n = tree_to_uhwi (nelts);
+	  sprintf (buf, "%s%llu", code, (unsigned long long)n);
+	  spec += buf;
+	  break;
+	}
+
+      /* Each variable VLA bound is represented by a dollar sign.  */
+      spec += "$";
+      vbchain = tree_cons (NULL_TREE, nelts, vbchain);
+    }
+
+  if (spec.empty ())
+    return attrs;
+
+  spec.insert (0, "[");
+  spec += ']';
+
+  tree acsstr = build_string (spec.length () + 1, spec.c_str ());
+  tree args = tree_cons (NULL_TREE, acsstr, vbchain);
+  tree name = get_identifier ("arg spec");
+  return tree_cons (name, args, attrs);
+}
+
 /* Given a parsed parameter declaration, decode it into a PARM_DECL
    and push that on the current scope.  EXPR is a pointer to an
    expression that needs to be evaluated for the side effects of array
@@ -5636,12 +5772,12 @@ void
 push_parm_decl (const struct c_parm *parm, tree *expr)
 {
   tree attrs = parm->attrs;
-  tree decl;
-
-  decl = grokdeclarator (parm->declarator, parm->specs, PARM, false, NULL,
-			 &attrs, expr, NULL, DEPRECATED_NORMAL);
+  tree decl = grokdeclarator (parm->declarator, parm->specs, PARM, false, NULL,
+			      &attrs, expr, NULL, DEPRECATED_NORMAL);
   if (decl && DECL_P (decl))
     DECL_SOURCE_LOCATION (decl) = parm->loc;
+
+  attrs = get_parm_array_spec (parm, attrs);
   decl_attributes (&decl, attrs, 0);
 
   decl = pushdecl (decl);
@@ -9227,6 +9363,7 @@ start_function (struct c_declspecs *declspecs, struct c_declarator *declarator,
   old_decl = lookup_name_in_scope (DECL_NAME (decl1), current_scope);
   if (old_decl && TREE_CODE (old_decl) != FUNCTION_DECL)
     old_decl = NULL_TREE;
+
   current_function_prototype_locus = UNKNOWN_LOCATION;
   current_function_prototype_built_in = false;
   current_function_prototype_arg_types = NULL_TREE;
@@ -9357,12 +9494,22 @@ start_function (struct c_declspecs *declspecs, struct c_declarator *declarator,
 		 "%qD is normally a non-static function", decl1);
     }
 
+  tree parms = current_function_arg_info->parms;
+  if (old_decl)
+    {
+      location_t origloc = DECL_SOURCE_LOCATION (old_decl);
+      warn_parm_array_mismatch (origloc, old_decl, parms);
+    }
+
   /* Record the decl so that the function name is defined.
      If we already have a decl for this name, and it is a FUNCTION_DECL,
      use the old decl.  */
 
   current_function_decl = pushdecl (decl1);
 
+  if (tree access = build_attr_access_from_parms (parms, false))
+    decl_attributes (&current_function_decl, access, 0, old_decl);
+
   push_scope ();
   declare_parm_level ();
 
diff --git a/gcc/c/c-parser.c b/gcc/c/c-parser.c
index 7961cbc98bb..f5d18195c14 100644
--- a/gcc/c/c-parser.c
+++ b/gcc/c/c-parser.c
@@ -68,6 +68,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "intl.h"
 #include "c-family/name-hint.h"
 #include "tree-iterator.h"
+#include "tree-pretty-print.h"
 #include "memmodel.h"
 #include "c-family/known-headers.h"
 
@@ -2296,13 +2297,11 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok,
 		  c_parser_skip_to_end_of_block_or_statement (parser);
 		  return;
 		}
-	      tree d = start_decl (declarator, specs, false,
-				   chainon (postfix_attrs,
-					    all_prefix_attrs));
-	      if (d
-		  && TREE_CODE (d) == FUNCTION_DECL
-		  && DECL_ARGUMENTS (d) == NULL_TREE
-		  && DECL_INITIAL (d) == NULL_TREE)
+
+	      location_t lastloc = UNKNOWN_LOCATION;
+	      tree attrs = chainon (postfix_attrs, all_prefix_attrs);
+	      tree d = start_decl (declarator, specs, false, attrs, &lastloc);
+	      if (d && TREE_CODE (d) == FUNCTION_DECL)
 		{
 		  /* Find the innermost declarator that is neither cdk_id
 		     nor cdk_attrs.  */
@@ -2331,10 +2330,18 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok,
 			gcc_unreachable ();
 		      }
 
-		  /* If it exists and is cdk_function, use its parameters.  */
+		  /* If it exists and is cdk_function declaration whose
+		     arguments have not been set yet, use its arguments.  */
 		  if (last_non_id_attrs
 		      && last_non_id_attrs->kind == cdk_function)
-		    DECL_ARGUMENTS (d) = last_non_id_attrs->u.arg_info->parms;
+		    {
+		      tree parms = last_non_id_attrs->u.arg_info->parms;
+		      if (DECL_ARGUMENTS (d) == NULL_TREE
+			  && DECL_INITIAL (d) == NULL_TREE)
+			DECL_ARGUMENTS (d) = parms;
+
+		      warn_parm_array_mismatch (lastloc, d, parms);
+		    }
 		}
 	      if (omp_declare_simd_clauses.exists ())
 		{
@@ -2363,7 +2370,7 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok,
 	      if (d)
 		finish_decl (d, UNKNOWN_LOCATION, NULL_TREE,
 			     NULL_TREE, asm_name);
-	      
+
 	      if (c_parser_next_token_is_keyword (parser, RID_IN))
 		{
 		  if (d)
diff --git a/gcc/c/c-tree.h b/gcc/c/c-tree.h
index 10938cf0857..3adc29dafc4 100644
--- a/gcc/c/c-tree.h
+++ b/gcc/c/c-tree.h
@@ -609,7 +609,7 @@ extern void shadow_tag_warned (const struct c_declspecs *, int);
 extern tree start_enum (location_t, struct c_enum_contents *, tree);
 extern bool start_function (struct c_declspecs *, struct c_declarator *, tree);
 extern tree start_decl (struct c_declarator *, struct c_declspecs *, bool,
-			tree);
+			tree, location_t * = NULL);
 extern tree start_struct (location_t, enum tree_code, tree,
 			  class c_struct_parse_info **);
 extern void store_parm_decls (void);
diff --git a/gcc/calls.c b/gcc/calls.c
index 77ab8647a10..79aa2c09b29 100644
--- a/gcc/calls.c
+++ b/gcc/calls.c
@@ -2463,7 +2463,7 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
 	  if (POINTER_TYPE_P (type))
 	    {
 	      access->ptr = args[i].tree_value;
-	      gcc_assert (access->size == NULL_TREE);
+	      // A nonnull ACCESS->SIZE contains VLA bounds.  */
 	    }
 	  else
 	    {
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index dea1e1866a4..3d309319c5d 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -5200,6 +5200,7 @@ Options} and @ref{Objective-C and Objective-C++ Dialect Options}.
 
 @gccoptlist{-Waddress   @gol
 -Warray-bounds=1 @r{(only with} @option{-O2}@r{)}  @gol
+-Warray-parameter=2 @r{(C and Objective-C only)} @gol
 -Wbool-compare  @gol
 -Wbool-operation  @gol
 -Wc++11-compat  -Wc++14-compat  @gol
@@ -5251,6 +5252,7 @@ Options} and @ref{Objective-C and Objective-C++ Dialect Options}.
 -Wunused-label     @gol
 -Wunused-value     @gol
 -Wunused-variable  @gol
+-Wvla-parameter @r{(C and Objective-C only)} @gol
 -Wvolatile-register-var  @gol
 -Wzero-length-bounds}
 
@@ -7094,6 +7096,54 @@ pointers. This warning level may give a larger number of
 false positives and is deactivated by default.
 @end table
 
+@item -Warray-parameter
+@itemx -Warray-parameter=@var{n}
+@opindex Wno-array-parameter
+Warn about redeclarations of functions involving arguments of array or
+pointer types of inconsistent kinds or forms, and enable the detection
+of out-of-bounds accesses to such parameters by warnings such as
+@option{-Warray-bounds}.
+
+If the first function declaration uses the array form the bound specified
+in the array is assumed to be the minimum number of elements expected to
+be provided in calls to the function and the maximum number of elements
+accessed by it.  Failing to provide arguments of sufficient size or accessing
+more than the maximum number of elements may be diagnosed by warnings such
+as @option{-Warray-bounds}.  At level 1 the warning diagnoses inconsistencies
+involving array parameters declared using the @code{T[static N]} form.
+
+For example, the warning triggers for the following redeclarations because
+the first one allows an array of any size to be passed to @code{f} while
+the second one with the keyword @code{static} specifies that the array
+argument must have at least four elements.
+
+@smallexample
+void f (int[static 4]);
+void f (int[]);           // warning (inconsistent array form)
+
+void g (void)
+@{
+  int *p = (int *)malloc (4);
+  f (p);                  // warning (array too small)
+  @dots{}
+@}
+@end smallexample
+
+At level 2 the warning also triggers for redeclarations involving any other
+inconsistency in array or pointer argument forms denoting array sizes.
+Pointers and arrays of unspecified bound are considered equivalent and do
+not trigger a warning.
+
+@smallexample
+void g (int*);
+void g (int[]);     // no warning
+void g (int[8]);    // warning (inconsistent array bound)
+@end smallexample
+
+@option{-Warray-parameter=2} is included in @option{-Wall}.  The
+@option{-Wvla-parameter} option triggers warnings for similar inconsistencies
+involving Variable Length Array arguments.
+
 @item -Wattribute-alias=@var{n}
 @itemx -Wno-attribute-alias
 @opindex Wattribute-alias
@@ -8517,6 +8567,44 @@ See also @option{-Walloca-larger-than=@var{byte-size}}.
 Disable @option{-Wvla-larger-than=} warnings.  The option is equivalent
 to @option{-Wvla-larger-than=}@samp{SIZE_MAX} or larger.
 
+@item -Wvla-parameter
+@opindex Wno-vla-parameter
+Warn about redeclarations of functions involving arguments of Variable
+Length Array types of inconsistent kinds or forms, and enable the detection
+of out-of-bounds accesses to such parameters by warnings such as
+@option{-Warray-bounds}.
+
+If the first function declaration uses the VLA form the bound specified
+in the array is assumed to be the minimum number of elements expected to
+be provided in calls to the function and the maximum number of elements
+accessed by it.  Failing to provide arguments of sufficient size or
+accessing more than the maximum number of elements may be diagnosed.
+
+For example, the warning triggers for the following redeclarations because
+the first one allows an array of any size to be passed to @code{f} while
+the second one specifies that the array argument must have at least @code{n}
+elements.  In addition, calling @code{f} with the assotiated VLA bound
+parameter in excess of the actual VLA bound triggers a warning as well.
+
+@smallexample
+void f (int n, int[n]);
+void f (int, int[]);     // warning: argument 2 previously declared as a VLA
+
+void g (int n)
+@{
+    if (n > 4)
+      return;
+    int a[n];
+    f (sizeof a, a);     // warning: access to a by f may be out of bounds
+  @dots{}
+@}
+
+@end smallexample
+
+@option{-Wvla-parameter} is included in @option{-Wall}.  The
+@option{-Warray-parameter} option triggers warnings for similar problems
+involving ordinary array arguments.
+
 @item -Wvolatile-register-var
 @opindex Wvolatile-register-var
 @opindex Wno-volatile-register-var
diff --git a/gcc/testsuite/c-c++-common/Warray-bounds-6.c b/gcc/testsuite/c-c++-common/Warray-bounds-6.c
index 6611d5cd916..5e787360314 100644
--- a/gcc/testsuite/c-c++-common/Warray-bounds-6.c
+++ b/gcc/testsuite/c-c++-common/Warray-bounds-6.c
@@ -1,8 +1,12 @@
-/* PR tree-optimization/86614 */
-/* { dg-do compile } */
-/* { dg-options "-O2 -Warray-bounds" } */
+/* PR tree-optimization/86614 - duplicate -Warray-bounds for a strncpy
+   call with out-of-bounds offset
+   { dg-do compile }
+   { dg-options "-O2 -Warray-bounds" } */
 
-extern char *strncpy (char *, const char *, __SIZE_TYPE__);
+#if __cplusplus
+extern "C"
+#endif
+char *strncpy (char *, const char *, __SIZE_TYPE__);
 
 void sink (void *);
 
@@ -12,7 +16,8 @@ void g (const char *s, unsigned n)
 {
   int i = (char *)a[1].b - (char *)a + 1;
   char *d = a[1].b;
-  /* Ensure the same bug is not diagnosed more than once.  */
-  strncpy (d + i, s, n);	/* { dg-warning "array subscript \[0-9]+ is outside array bounds of" } */
-				/* { dg-bogus "offset \[0-9]+ is out of the bounds \\\[0, \[0-9]+\\\] of object 'a' with type" "" { target *-*-* } .-1 } */
+  /* Verify the bug is diagnosed exactly once, using either form
+     of the warning.  */
+  strncpy (d + i, s, n);	/* { dg-warning "array subscript \[0-9]+ is outside array bounds|offset \[0-9]+ is out of the bounds" } */
+				/* { dg-bogus "offset \[0-9]+ is out of the bounds|array subscript \[0-9]+ is outside array bounds" "" { target *-*-* } .-1 } */
 }
diff --git a/gcc/testsuite/gcc.dg/Warray-parameter-2.c b/gcc/testsuite/gcc.dg/Warray-parameter-2.c
new file mode 100644
index 00000000000..88f20e203ce
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Warray-parameter-2.c
@@ -0,0 +1,45 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+   { dg-do compile }
+   { dg-options "-Wall" } */
+
+// Reduced from Glibc.
+
+typedef struct FILE FILE;
+
+int vfprintf (FILE*, const char*, __builtin_va_list);
+int vfprintf (FILE*, const char*, __builtin_va_list);   // { dg-bogus "-Warray-parameter" }
+int vfprintf (FILE*, const char*, __builtin_va_list);
+
+int vfscanf (FILE*, const char*, __builtin_va_list);
+int vfscanf (FILE*, const char*, __builtin_va_list);    // { dg-bogus "-Warray-parameter" }
+int vfscanf (FILE*, const char*, __builtin_va_list);
+
+
+/* Verify that mismatches in array/to pointer to va_list are still
+   diagnosed.  */
+
+int fva (__builtin_va_list);
+int fva (__builtin_va_list);
+
+int fpva_a1 (__builtin_va_list*);
+int fpva_a1 (__builtin_va_list[1]);     // { dg-warning "\\\[-Warray-parameter" }
+
+int fpva_a_ (__builtin_va_list*);
+int fpva_a_ (__builtin_va_list[]);
+int fpva_a_ (__builtin_va_list*);
+int fpva_a_ (__builtin_va_list[]);
+
+/* Also verify that a mismatch between a pointer and a one-element
+   array are diagnosed.  This is pervasive in Glibc headers but
+   making an exception for it would leave no way to express
+   the requirement that a function take at least one argument
+   by reference.  */
+
+struct __jmp_buf_tag;
+int __sigsetjmp (struct __jmp_buf_tag*, int);
+
+struct __jmp_buf_tag { };
+typedef struct __jmp_buf_tag jmp_buf[1];
+
+int __sigsetjmp (struct __jmp_buf_tag[1], int);   // { dg-warning "\\\[-Warray-parameter" }
diff --git a/gcc/testsuite/gcc.dg/Warray-parameter-3.c b/gcc/testsuite/gcc.dg/Warray-parameter-3.c
new file mode 100644
index 00000000000..cbf3e9339f5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Warray-parameter-3.c
@@ -0,0 +1,89 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+   { dg-do compile }
+   { dg-options "-Wall -Warray-parameter=1" } */
+
+/* Verify that at level 1 mismatches in the bounds of ordinary array
+   parameters don't trigger -Warray-parameter.  */
+void fax (int[]);
+void fax (int[0]);
+void fax (int[1]);
+void fax (int[2]);
+void fax (int[3]);
+
+/* Same as above but starting with an array with a specified bound.  */
+void gax (int[3]);
+void gax (int[2]);
+void gax (int[1]);
+void gax (int[0]);
+void gax (int[]);
+
+/* Same for multidimensional arrays.  */
+void fax_y (int[][3]);
+void fax_y (int[0][3]);
+void fax_y (int[1][3]);
+void fax_y (int[2][3]);
+void fax_y (int[3][3]);
+
+/* Same as above but starting with an array with a specified bound.  */
+void gax_y (int[3][5]);
+void gax_y (int[2][5]);
+void gax_y (int[1][5]);
+void gax_y (int[0][5]);
+void gax_y (int[][5]);
+
+/* Exercise VLAs with a mismatch in the bound for an ordinary array.  */
+void fvlax_y (int n, int[][n]);
+void fvlax_y (int n, int[0][n]);
+void fvlax_y (int n, int[1][n]);
+void fvlax_y (int n, int[2][n]);
+void fvlax_y (int n, int[3][n]);
+
+void fvlaxn_y (int n, int[][n]);
+void fvlaxn_y (int n, int[0][n]);
+void fvlaxn_y (int n, int[1][n]);
+void fvlaxn_y (int n, int[2][n]);
+void fvlaxn_y (int n, int[3][n]);
+
+void fvlaxx_y (int[][*]);
+void fvlaxx_y (int[0][*]);
+void fvlaxx_y (int[1][*]);
+void fvlaxx_y (int[2][*]);
+void fvlaxx_y (int[3][*]);
+
+/* Verify that mismatches in the bounds of array parameters declared
+   static do trigger -Warray-parameter.  */
+void fas1 (int[static 1]);    // { dg-message "previously declared as 'int\\\[static 1]'" }
+void fas1 (int[static 2]);    // { dg-warning "\\\[-Warray-parameter=" }
+
+
+/* Also verify that -Warray-bounds doesn't trigger for ordinary array
+   parameters...  */
+#pragma GCC optimize "2"
+
+__attribute__ ((noipa)) void
+gca3 (char a[3])
+{
+  a[0] = 0; a[1] = 1; a[2] = 2; a[3] = 3;
+}
+
+__attribute__ ((noipa)) void
+gia3 (int a[3])
+{
+  a[0] = 0; a[1] = 1; a[2] = 2; a[3] = 3;
+}
+
+/* ...but does for static arrays.  */
+__attribute__ ((noipa)) void
+gcas3 (char a[static 3])
+{
+  a[0] = 0; a[1] = 1; a[2] = 2;
+  a[3] = 3;                   // { dg-warning "\\\[-Warray-bounds" }
+}
+
+__attribute__ ((noipa)) void
+gias3 (int a[static 3])
+{
+  a[0] = 0; a[1] = 1; a[2] = 2;
+  a[3] = 3;                   // { dg-warning "\\\[-Warray-bounds" }
+}
diff --git a/gcc/testsuite/gcc.dg/Warray-parameter-4.c b/gcc/testsuite/gcc.dg/Warray-parameter-4.c
new file mode 100644
index 00000000000..52de3bc65bf
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Warray-parameter-4.c
@@ -0,0 +1,119 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+
+   Verify warnings for multidimensional arrays.
+
+   { dg-do compile }
+   { dg-options "-Wall -Warray-parameter=2" } */
+
+// Verify that equivalent forms don't tigger a warning.
+
+typedef int IA1[1];
+typedef int IA1x_[][1];
+typedef int IA1x0[0][1];
+
+void fia_x1 (int[][1]);
+void fia_x1 (IA1[0]);
+void fia_x1 (IA1x_);
+void fia_x1 (IA1x0);
+void fia_x1 (int[0][1]);
+void fia_x1 (int[static 0][1]);
+
+// Same as above one more time.
+void fia_x1 (int[][1]);
+void fia_x1 (IA1[0]);
+void fia_x1 (IA1x_);
+void fia_x1 (IA1x0);
+void fia_x1 (int[0][1]);
+void fia_x1 (int[static 0][1]);
+
+
+void fia1x1 (int[1][1]);
+void fia1x1 (int[1][1]);
+void fia1x1 (int[static 1][1]);
+
+void fia2x1 (int[2][1]);
+void fia2x1 (int[2][1]);
+void fia2x1 (int[static 2][1]);
+
+void fia1x2 (int[1][2]);
+void fia1x2 (int[1][2]);
+void fia1x2 (int[static 1][2]);
+
+void fia1x1_2x1 (int[1][1]);          // { dg-message "previously declared as 'int\\\[1]\\\[1]'" }
+void fia1x1_2x1 (int[2][1]);          // { dg-warning "\\\[-Warray-parameter" }
+void fia1x1_2x1 (int[static 1][1]);
+void fia1x1_2x1 (int[static 2][1]);   // { dg-warning "\\\[-Warray-parameter" }
+
+
+void fia2x1_1x1 (int[2][1]);          // { dg-message "previously declared as 'int\\\[2]\\\[1]'" }
+void fia2x1_1x1 (int[1][1]);          // { dg-warning "\\\[-Warray-parameter" }
+void fia2x1_1x1 (int[2][1]);
+void fia2x1_1x1 (int[static 1][1]);   // { dg-warning "\\\[-Warray-parameter" }
+void fia2x1_1x1 (int[static 2][1]);
+
+
+extern int n1, n2;
+
+void fca_xn1 (char[][n1]);
+void fca_xn1 (char[0][n1]);
+void fca_xn1 (char[static 0][n1]);
+
+void fca1xn1_2xn1 (char[1][n1]);
+void fca1xn1_2xn1 (char[2][n1]);        // { dg-warning "\\\[-Warray-parameter" }
+void fca1xn1_2xn1 (char[1][n1]);
+void fca1xn1_2xn1 (char[static 1][n1]);
+void fca1xn1_2xn1 (char[static 2][n1]); // { dg-warning "\\\[-Warray-parameter" }
+
+
+/* Exercise VLAs with a mismatch in the bound for an ordinary array.  */
+void fvlax_y (int n, int[][n]);
+void fvlax_y (int n, int[0][n]);
+void fvlax_y (int n, int[1][n]);        // { dg-warning "argument 2 of type 'int\\\[1]\\\[n]' with mismatched bound" }
+void fvlax_y (int n, int[2][n]);        // { dg-warning "argument 2 of type 'int\\\[2]\\\[n]' with mismatched bound" }
+void fvlax_y (int n, int[3][n]);        // { dg-warning "argument 2 of type 'int\\\[3]\\\[n]' with mismatched bound" }
+
+void fvlaxn_y (int n, int[][n]);
+void fvlaxn_y (int n, int[0][n]);
+void fvlaxn_y (int n, int[1][n]);       // { dg-warning "\\\[-Warray-parameter" }
+void fvlaxn_y (int n, int[2][n]);       // { dg-warning "\\\[-Warray-parameter" }
+void fvlaxn_y (int n, int[3][n]);       // { dg-warning "\\\[-Warray-parameter" }
+
+void fvlaxx_y (int[][*]);
+void fvlaxx_y (int[0][*]);
+void fvlaxx_y (int[1][*]);              // { dg-warning "\\\[-Warray-parameter" }
+void fvlaxx_y (int[2][*]);              // { dg-warning "\\\[-Warray-parameter" }
+void fvlaxx_y (int[3][*]);              // { dg-warning "\\\[-Warray-parameter" }
+
+
+// Verify an array of pointers to an array of function pointers.
+
+void ffpa7_5 (void (* (* (* [7])[5])(void))(void));
+// { dg-message "previously declared as 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[7]\\\)\\\[5]\\\)\\\(void\\\)\\\)\\\(void\\\)'" "note" { target *-*-* } .-1 }
+void ffpa7_5 (void (* (* (* [6])[5])(void))(void));
+// { dg-warning "argument 1 of type 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[6]\\\)\\\[5]\\\)\\\(void\\\)\\\)\\\(void\\\)' with mismatched bound" "" { target *-*-* } .-1 }
+void ffpa7_5 (void (* (* (* [])[5])(void))(void));
+// { dg-warning "argument 1 of type 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[]\\\)\\\[5]\\\)\\\(void\\\)\\\)\\\(void\\\)' with mismatched bound" "" { target *-*-* } .-1 }
+void ffpa7_5 (void (* (* (* (*))[5])(void))(void));
+// { dg-warning "argument 1 of type 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\*\\\)\\\[5]\\\)\\\(void\\\)\\\)\\\(void\\\)' declared as a pointer" "" { target *-*-* } .-1 }
+
+// Same as above but with array of pointers to a VLA of function pointers.
+void ffpa7_n1 (void (* (* (* [7])[n1])(void))(void));
+// { dg-message "previously declared as 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[7]\\\)\\\[n1]\\\)\\\(void\\\)\\\)\\\(void\\\)'" "note" { target *-*-* } .-1 }
+void ffpa7_n1 (void (* (* (* [8])[n1])(void))(void));
+// { dg-warning "argument 1 of type 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[8]\\\)\\\[n1]\\\)\\\(void\\\)\\\)\\\(void\\\)' with mismatched bound" "" { target *-*-* } .-1 }
+
+void ffpa9_x (void (* (* (* [9])[*])(void))(void));
+// { dg-message "previously declared as 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[9]\\\)\\\[\\\*]\\\)\\\(void\\\)\\\)\\\(void\\\)'" "pr?????" { xfail *-*-* } .-1 }
+// { dg-message "previously declared as 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[9]\\\)\\\[0]\\\)\\\(void\\\)\\\)\\\(void\\\)'" "" { target *-*-* } .-2 }
+void ffpa9_x (void (* (* (* [8])[*])(void))(void));
+// { dg-warning "argument 1 of type 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[8]\\\)\\\[\\\*]\\\)\\\(void\\\)\\\)\\\(void\\\)' with mismatched bound" "pr?????" { xfail *-*-* } .-1 }
+// { dg-warning "argument 1 of type 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[8]\\\)\\\[0]\\\)\\\(void\\\)\\\)\\\(void\\\)' with mismatched bound"  "" { target *-*-* } .-2 }
+
+/* Verify a three-dimensional array of pointers to two-dimensional arrays
+   of pointers to function pointers.  */
+
+void ffpa7_5_3 (void (* (* (* (* [7])[5])[3])(void))(void));
+// { dg-message "previously declared as 'void ?\\\(\\\* ?\\\(\\\* ?\\\(\\\* ?\\\(\\\* ?\\\[7]\\\)\\\[5]\\\)\\\[3]\\\)\\\(void\\\)\\\)\\\(void\\\)'" "note" { target *-*-* } .-1 }
+void ffpa7_5_3 (void (* (* (* (* [1])[5])[3])(void))(void));
+// { dg-warning "argument 1 of type 'void ?\\\(\\\* ?\\\(\\\* ?\\\(\\\* ?\\\(\\\* ?\\\[1]\\\)\\\[5]\\\)\\\[3]\\\)\\\(void\\\)\\\)\\\(void\\\)' with mismatched bound" "" { target *-*-* } .-1 }
diff --git a/gcc/testsuite/gcc.dg/Warray-parameter.c b/gcc/testsuite/gcc.dg/Warray-parameter.c
new file mode 100644
index 00000000000..1a6a3afe181
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Warray-parameter.c
@@ -0,0 +1,173 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+
+   Verify that -Warray-parameter diagnoses mismatches in array (and
+   pointer) arrguments between redeclarations of the same function.
+   Also verify that the array/pointer argument form in a mismatched
+   redeclaration doesn't override the form in the initial declaration.
+
+   { dg-do compile }
+   { dg-options "-Wall -Warray-parameter -Wno-vla-paramater" } */
+
+/* Redclarations with the same or equivalent array form should not
+   be dianosed.  T[0] is diagnosed by -Wpedantic for being invalid
+   C so there's little point in also warning for the difference in
+   array form.  */
+void f1vpp (void**);
+void f1vpp (void*[]);
+void f1vpp (void*[0]);
+
+void f1ia_ (int[]);
+void f1ia_ (int[]);
+void f1ia_ (int[0]);
+/* Verify the unused attribute still has an effect.  */
+void f1ia_ (int a[0] __attribute__ ((unused))) { }
+void f1ia_ (int[]);
+
+void f1ia_p (int[]);
+void f1ia_p (int*);
+void f1ia_p (int *p  __attribute__ ((unused))) { }
+void f1ia_p (int[]);
+
+void f1p_ia (const int*);
+void f1p_ia (const int[]);
+void f1p_ia (const int *p __attribute__ ((unused))) { }
+void f1p_ia (const int[]);
+
+void f1ia1 (int[1]);
+void f1ia1 (int[1]);
+void f1ia1 (int[2 - 1]);
+
+void f1ias2 (int[static 2]);
+void f1ias2 (int[static 2]);
+void f1ias2 (int[static 1 + 1]);
+void f1ias2 (int a[static 3 - 1]) { (void)&a; }
+
+void f1ipa_ (int*[]);
+void f1ipa_ (int*[]);
+void f1ipa_ (int*[0]);
+
+void f1ia1_x (int[1]);          // { dg-message "previously declared as 'int\\\[1]'" }
+void f1ia1_x (int[]);           // { dg-warning "argument 1 of type 'int\\\[]' with mismatched bound" }
+void f1ia1_x (int[]);           // { dg-warning "argument 1 of type 'int\\\[]' with mismatched bound" }
+void f1ia1_x (int[1]);
+void f1ia1_x (int[2]);          // { dg-warning "argument 1 of type 'int\\\[2]' with mismatched bound" }
+void f1ia1_x (int[1]);
+void f1ia1_x (int[3]);          // { dg-warning "argument 1 of type 'int\\\[3]' with mismatched bound" }
+void f1ia1_x (int a[1] __attribute__ ((unused))) { }
+
+
+void f1ias2_s3 (int[static 2]); // { dg-message "previously declared as 'int\\\[static 2]'" }
+void f1ias2_s3 (int[static 3]); // { dg-warning "argument 1 of type 'int\\\[static 3]' with mismatched bound" }
+/* Verify the unused attribute still has an effect and doesn't interfere
+   with the warning.  */
+void f1ias2_s3 (int a[static 3] __attribute__ ((unused))) { }  // { dg-warning "argument 1 of type 'int\\\[static 3]' with mismatched bound" }
+
+
+/* Ordinary T[N] and T[static N] forms are both effectively treated
+   the same but strictly have different meanings so they are diagnosed.
+   It might be worth splitting the warning into two levels and having
+   only the higher level treat the ordinary form as T[static N].  */
+
+void f1ia3_s4 (int[3]);         // { dg-message "previously declared as 'int\\\[3]'" }
+void f1ia3_s4 (int[static 4]);  // { dg-warning "argument 1 of type 'int\\\[static 4]' with mismatched bound" }
+void f1ia3_s4 (int[3]);
+
+
+void f1ias4_5 (int[static 4]);  // { dg-message "previously declared as 'int\\\[static 4]'" }
+void f1ias4_5 (int[5]);         // { dg-warning "argument 1 of type 'int\\\[5]' with mismatched bound" }
+void f1ias4_5 (int[static 4]);
+
+
+void f1ia_1 (int[]);            // { dg-message "previously declared as 'int\\\[]'" }
+void f1ia_1 (int[1]);           // { dg-warning "argument 1 of type 'int\\\[1]' with mismatched bound" }
+void f1ia_1 (int[]);
+
+
+void f1ca_ (char[]);            // { dg-message "previously declared as 'char\\\[]'" }
+void f1ca_ (char[2]);           // { dg-warning "argument 1 of type 'char\\\[2]' with mismatched bound" }
+void f1ca_ (char[]);
+
+
+void f1csp (const short*);      // { dg-message "previously declared as 'const short int ?\\\*'" }
+void f1csp (const short[3]);    // { dg-warning "argument 1 of type 'const short int\\\[3]' with mismatched bound" }
+void f1csp (const short*);
+
+
+void f1ia2 (int[2]);            // { dg-message "previously declared as 'int\\\[2]'" }
+void f1ia2 (int[1]);            // { dg-warning "argument 1 of type 'int\\\[1]' with mismatched bound" }
+void f1ia2 (int[2]);
+
+
+void f1cvla2 (const volatile long[3]);  // { dg-message "previously declared as 'const volatile long int\\\[3]'" }
+void f1cvla2 (const volatile long[2]);  // { dg-warning "argument 1 of type 'const volatile long int\\\[2]' with mismatched bound" }
+void f1cvla2 (const volatile long[3]);
+void f1cvla2 (const volatile long[restrict 4]); // { dg-warning "argument 1 of type 'const volatile long int\\\[restrict 4]' with mismatched bound" }
+
+
+void f1afa4 (_Atomic float[3]);         // { dg-message "previously declared as an array '_Atomic float ?\\\[3]'" }
+void f1afa4 (_Atomic float*);           // { dg-warning "argument 1 of type '_Atomic float ?\\\*' declared as a pointer" }
+void f1afa4 (_Atomic float[3]);
+
+void f1ipa1_a2 (int*[1]);       // { dg-message "previously declared as 'int \\\*\\\[1]'" }
+void f1ipa1_a2 (int*[2]);       // { dg-warning "argument 1 of type 'int \\\*\\\[2]' with mismatched bound" }
+void f1ipa1_a2 (int*[1]);
+
+
+typedef int IA1[1];
+typedef int IA2[2];
+typedef int IA3[3];
+
+void f1IA1_A2 (IA1);            // { dg-message "previously declared as 'int\\\[1]'" }
+void f1IA1_A2 (IA2);            // { dg-warning "argument 1 of type 'int\\\[2]' with mismatched bound" }
+void f1IA1_A2 (IA1);
+void f1IA1_A2 (int[2]);         // { dg-warning "argument 1 of type 'int\\\[2]' with mismatched bound" }
+
+
+void f1IA1_A3 (IA1 ia1);        // { dg-message "previously declared as 'int\\\[1]'" }
+void f1IA1_A3 (IA3 ia3);        // { dg-warning "argument 1 of type 'int\\\[3]' with mismatched bound" }
+void f1IA1_A3 (IA1 ia1);
+
+
+void f1IA2_A3 (IA2 a);          // { dg-message "previously declared as 'int\\\[2]'" }
+void f1IA2_A3 (IA3 a);          // { dg-warning "argument 1 of type 'int\\\[3]' with mismatched bound" }
+void f1IA2_A3 (IA2 a);
+
+
+// Verify multiple array arguments.
+
+void f2a2_a3_3_3 (int[2], int[3]);  // { dg-message "previously declared as 'int\\\[2]'" }
+void f2a2_a3_3_3 (int[2], int[3]);
+void f2a2_a3_3_3 (int[3], int[3]);  // { dg-warning "argument 1 of type 'int\\\[3]' with mismatched bound" }
+
+
+void f2a2_a3_2_4 (int[2], int[3]);  // { dg-message "previously declared as 'int\\\[3]'" }
+void f2a2_a3_2_4 (int[2], int[4]);  // { dg-warning "argument 2 of type 'int\\\[4]' with mismatched bound" }
+
+
+/* Verify that pointers to arrays and arrays of arrays are differentiated
+   the same way as pointers and arrays of other types.  */
+typedef int A[1];
+typedef A *PA;
+
+void fpia1 (A*);                // { dg-message "previously declared as 'int ?\\(\\\*\\)\\\[1]'" }
+void fpia1 (A[1]);              // { dg-warning "argument 1 of type 'int\\\[1]\\\[1]' with mismatched bound" }
+void fpia1 (PA);
+void fpia1 (int(*)[1]);
+void fpia1 (int[][1]);
+
+void f1vpa1 (void*[][1]);
+void f1vpa1 (void*[0][1]);
+
+/* Verify that attributes with arrays don't cause unwanted warnings and
+   don't suppress intended ones.  */
+
+#define ALIGN(N)__attribute__ ((aligned (__alignof__ (char[N]))))
+
+void fatipa (int (* ALIGN (3)[2])); // { dg-message "previously declared as 'int \\\*\\\[2]'" }
+void fatipa (int (* ALIGN (4)[2]));
+void fatipa (int (* ALIGN (5)[2]));
+void fatipa (int (* ALIGN (7)[3])); // { dg-warning "argument 1 of type 'int \\\*\\\[3]' with mismatched bound" }
+
+void fatiap (int (* ALIGN (3))[2]);
+void fatiap (int (* ALIGN (5))[2]);
diff --git a/gcc/testsuite/gcc.dg/Wbuiltin-declaration-mismatch-9.c b/gcc/testsuite/gcc.dg/Wbuiltin-declaration-mismatch-9.c
index f0c1ce33267..da767b87700 100644
--- a/gcc/testsuite/gcc.dg/Wbuiltin-declaration-mismatch-9.c
+++ b/gcc/testsuite/gcc.dg/Wbuiltin-declaration-mismatch-9.c
@@ -10,3 +10,6 @@ void a (void)
 	  ""        /* { dg-warning "passing argument 2 of .sscanf. from incompatible pointer type" } */
 	  );
 }
+
+/* The scanf call may also trigger:
+   { dg-prune-output "-Wstringop-overflow" } */
diff --git a/gcc/testsuite/gcc.dg/Wvla-parameter-2.c b/gcc/testsuite/gcc.dg/Wvla-parameter-2.c
new file mode 100644
index 00000000000..fc7946477f6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wvla-parameter-2.c
@@ -0,0 +1,45 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+
+   Verify the -Wvla-parameter warnings correctly diagnose mismatches
+   between VLA arguments with mismatched bounds in redeclarations of
+   the same function.
+
+   { dg-do compile }
+   { dg-options "-Wall -Wvla-parameter" } */
+
+void fmn_a1n_axn (int n, int[1][n]);            // { dg-message "previously declared as 'int\\\[1]\\\[n]' with 1 variable bound" "note" }
+void fmn_a1n_axn (int n, int[*][n]);            // { dg-warning "argument 2 of type 'int\\\[\\\*]\\\[n]' declared with 2 variable bounds" }
+
+
+void fmn_axn_a2n (int n, int[*][n]);            // { dg-message "previously declared as 'int\\\[\\\*]\\\[n]' with 2 variable bounds" "note" }
+void fmn_axn_a2n (int n, int[2][n]);            // { dg-warning "argument 2 of type 'int\\\[2]\\\[n]' declared with 1 variable bound" }
+
+
+void fmn_amn_axn (int m, int n, int[m][n]);     // { dg-message "previously declared as 'int\\\[m]\\\[n]' with 0 unspecified variable bounds" "note" }
+void fmn_amn_axn (int m, int n, int[*][n]);     // { dg-warning "argument 3 of type 'int\\\[\\\*]\\\[n]' declared with 1 unspecified variable bound" }
+
+// Same as above but a different function name.
+void gmn_amn_axn (int m, int n, int[m][n]);     // { dg-message "previously declared as 'int\\\[m]\\\[n]' with 0 unspecified variable bounds" "note" }
+void gmn_amn_axn (int m, int n, int[*][n]);     // { dg-warning "argument 3 of type 'int\\\[\\\*]\\\[n]' declared with 1 unspecified variable bound" }
+
+
+int n1, n2, n3, n4, n5, n6, n7, n8, n9;
+void f (int[n1][2][n3][4][n5][6][n7][8][n9]);   // { dg-message "previously declared as 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[n5]\\\[6]\\\[n7]\\\[8]\\\[n9]' with 0 unspecified variable bounds" "note" }
+                                                // { dg-message "with 5 variable bounds" "note" { target *-*-* } .-1  }
+void f (int[n1][2][n3][4][n5][6][n7][8][n9]);
+
+/* Due to a limitation and because [*] is represented the same as [0]
+   only the most significant array bound is rendered as [*]; the others
+   are rendered as [0].  */
+void f (int[n1][2][n3][4][n5][6][n7][8][*]);    // { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[n5]\\\[6]\\\[n7]\\\[8]\\\[\\\*]' declared with 1 unspecified variable bound" "pr?????" { xfail *-*-* } }
+// { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[n5]\\\[6]\\\[n7]\\\[8]\\\[0]' declared with 1 unspecified variable bound" "pr?????" { target *-*-* } .-1 }
+void f (int[n1][2][n3][4][n5][6][*][8][n9]);   // { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[n5]\\\[6]\\\[\\\*]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" "pr?????" { xfail *-*-* } }
+// { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[n5]\\\[6]\\\[0]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" "pr?????" { target *-*-* } .-1 }
+void f (int[n1][2][n3][4][*][6][n7][8][n9]);   // { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[\\\*]\\\[6]\\\[n7]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" "pr?????" { xfail *-*-*} }
+// { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[0]\\\[6]\\\[n7]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" "pr?????" { target *-*-* } .-1 }
+void f (int[n1][2][*][4][n5][6][n7][8][n9]);   // { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[\\\*]\\\[4]\\\[n5]\\\[6]\\\[n7]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" "pr?????" { xfail *-*-* } }
+// { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[0]\\\[4]\\\[n5]\\\[6]\\\[n7]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" "pr?????" { target *-*-* } .-1 }
+void f (int[*][2][n3][4][n5][6][n7][8][n9]);   // { dg-warning "argument 1 of type 'int\\\[\\\*]\\\[2]\\\[n3]\\\[4]\\\[n5]\\\[6]\\\[n7]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" }
+
+void f (int[n1][n2][n3][n4][n5][n6][n7][n8][n9]);   // { dg-warning "argument 1 of type 'int\\\[n1]\\\[n2]\\\[n3]\\\[n4]\\\[n5]\\\[n6]\\\[n7]\\\[n8]\\\[n9]' declared with 9 variable bounds" }
diff --git a/gcc/testsuite/gcc.dg/Wvla-parameter.c b/gcc/testsuite/gcc.dg/Wvla-parameter.c
new file mode 100644
index 00000000000..c3d7b7fc0d6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wvla-parameter.c
@@ -0,0 +1,149 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+   Verify the -Wvla-parameter warnings correctly diagnose mismatches
+   between VLA and non-VLA arguments in redeclarations of the same
+   function.
+   Also verify that the array/pointer argument form in a mismatched
+   redeclaration doesn't override the form in the initial declaration.
+   { dg-do compile }
+   { dg-options "-Wall -Wvla-parameter" } */
+
+/* Verify that redeclaring an argument as a VLA with an unspecified
+   bound that was first declared as an ordinary array with an unspecified
+   bound triggers a warning.  */
+void f1ia_x (int[]);          // { dg-message "previously declared as an ordinary array 'int\\\[]'" "note" }
+void f1ia_x (int[*]);         // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ia_x (int[]);
+void f1ia_x (int[*]);         // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+/* Also verify that a definition of the same form as the first declaration
+   doesn't trigger a warning and doesn't prevent warnings for subsequent
+   mismatches.  */
+void f1ia_x (int a[]) { (void)&a;}
+void f1ia_x (int[*]);         // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+
+/* Repeat the above but starting with an ordinary array with a constant
+   bound.  */
+void f1ia1x (int[1]);          // { dg-message "previously declared as an ordinary array 'int\\\[1]'" "note" }
+void f1ia1x (int[*]);         // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ia1x (int a[1]) { (void)&a; }
+void f1ia1x (int[1]);
+void f1ia1x (int[*]);         // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+
+void f1ipx (int*);            // { dg-message "previously declared as a pointer 'int ?\\\*'" "note" }
+void f1ipx (int[*]);          // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ipx (int*);
+void f1ipx (int *p) { (void)&p; }
+void f1ipx (int[*]);          // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ipx (int*);
+
+void f2ipx (int*, int*);      // { dg-message "previously declared as a pointer 'int ?\\\*'" "note" }
+void f2ipx (int*, int[*]);    // { dg-warning "argument 2 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f2ipx (int*, int*);
+void f2ipx (int*, int[*]);    // { dg-warning "argument 2 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f2ipx (int *p, int *q) { (void)&p; (void)&q; }
+void f2ipx (int*, int[*]);    // { dg-warning "argument 2 of type 'int\\\[\\\*]' declared as a variable length array" }
+
+void f1ias2x (int[static 2]); // { dg-message "previously declared as an ordinary array 'int\\\[static 2]'" }
+void f1ias2x (int[*]);        // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ias2x (int[static 2]);
+void f1ias2x (int[*]);        // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ias2x (int a[static 2]) { (void)&a; }
+void f1ias2x (int[*]);        // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ias2x (int[static 2]);
+
+extern int nelts;
+
+void f1sa_var (short[]);      // { dg-message "previously declared as an ordinary array 'short int\\\[]'" }
+void f1sa_var (short[nelts]); // { dg-warning "argument 1 of type 'short int\\\[nelts]' declared as a variable length array" }
+void f1sa_var (short[]);
+void f1sa_var (short[nelts]); // { dg-warning "argument 1 of type 'short int\\\[nelts]' declared as a variable length array" }
+void f1sa_var (short a[]) { (void)&a; }
+void f1sa_var (short[nelts]); // { dg-warning "argument 1 of type 'short int\\\[nelts]' declared as a variable length array" }
+void f1sa_var (short[]);
+
+void f1sa_expr (int[]);           // { dg-message "previously declared as an ordinary array 'int\\\[]'" }
+void f1sa_expr (int[nelts + 1]);  // { dg-warning "argument 1 of type 'int\\\[nelts \\\+ 1]' declared as a variable length array" }
+void f1sa_expr (int[]);
+void f1sa_expr (int[nelts * 2]);  // { dg-warning "argument 1 of type 'int\\\[nelts \\\* 2]' declared as a variable length array" }
+void f1sa_expr (int a[]) { (void)&a; }
+void f1sa_expr (int[nelts / 3]);  // { dg-warning "argument 1 of type 'int\\\[nelts / 3]' declared as a variable length array" }
+void f1sa_expr (int[]);
+
+extern int f (int);
+
+void f1ia_f (int[]);          // { dg-message "previously declared as an ordinary array 'int\\\[]'" }
+void f1ia_f (int[f (1)]);     // { dg-warning "argument 1 of type 'int\\\[f *\\\(1\\\)]' declared as a variable length array" }
+void f1ia_f (int[]);
+void f1ia_f (int[f (2)]);     // { dg-warning "argument 1 of type 'int\\\[f *\\\(2\\\)]' declared as a variable length array" }
+void f1ia_f (int a[]) { (void)&a; }
+void f1ia_f (int[f (3)]);     // { dg-warning "argument 1 of type 'int\\\[f *\\\(3\\\)]' declared as a variable length array" }
+void f1ia_f (int[f (4)]);     // { dg-warning "argument 1 of type 'int\\\[f *\\\(4\\\)]' declared as a variable length array" }
+void f1ia_f (int[]);
+
+void f1iaf0_f1 (int[f (0)]);  // { dg-message "previously declared as 'int\\\[f *\\\(0\\\)]'" }
+void f1iaf0_f1 (int[f (1)]);  // { dg-warning "argument 1 of type 'int\\\[f *\\\(1\\\)]' declared with mismatched bound" }
+void f1iaf0_f1 (int[f (0)]);
+void f1iaf0_f1 (int[f (1)]);  // { dg-warning "argument 1 of type 'int\\\[f *\\\(1\\\)]' declared with mismatched bound" }
+void f1iaf0_f1 (int a[f (0)]) { (void)&a; }
+void f1iaf0_f1 (int[f (1)]);  // { dg-warning "argument 1 of type 'int\\\[f *\\\(1\\\)]' declared with mismatched bound" }
+void f1iaf0_f1 (int[f (0)]);
+
+void f1la_ (long[]);         // { dg-message "previously declared as an ordinary array 'long int\\\[]'" }
+void f1la_ (long[nelts]);    // { dg-warning "argument 1 of type 'long int\\\[nelts]' declared as a variable length array" }
+void f1la_ (long[]);
+void f1la_ (long a[nelts])   // { dg-warning "argument 1 of type 'long int\\\[nelts]' declared as a variable length array" }
+{ (void)&a; }
+void f1la_ (long[]);
+
+void f2ca_ (int, char[]);     // { dg-message "previously declared as an ordinary array 'char\\\[]'" }
+void f2ca_ (int n, char[n]);  // { dg-warning "argument 2 of type 'char\\\[n]' declared as a variable length array" }
+void f2ca_ (int, char[]);
+void f2ca_ (int n, char a[n]) // { dg-warning "argument 2 of type 'char\\\[n]' declared as a variable length array" }
+{ (void)&n; (void)&a; }
+
+void f2ia1_f (int n, int[n]);     // { dg-message "previously declared as 'int\\\[n]' with bound argument 1" }
+void f2ia1_f (int,   int[f (0)]); // { dg-warning "argument 2 of type 'int\\\[f *\\\(0\\\)]' declared with mismatched bound 'f *\\\(0\\\)'" }
+void f2ia1_f (int m, int[m]);
+void f2ia1_f (int,   int[f (1)]); // { dg-warning "argument 2 of type 'int\\\[f *\\\(1\\\)]' declared with mismatched bound 'f *\\\(1\\\)'" }
+void f2ia1_f (int x, int a[x]) { (void)&x; (void)&a; }
+void f2ia1_f (int,   int[f (2)]);   // { dg-warning "argument 2 of type 'int\\\[f *\\\(2\\\)]' declared with mismatched bound 'f *\\\(2\\\)'" }
+void f2ia1_f (int y, int[y]);
+
+void f2iaf_1 (int,   int[f (0)]); // { dg-message "previously declared as 'int\\\[f *\\\(0\\\)]'" }
+void f2iaf_1 (int n, int[n]);     // { dg-warning "argument 2 of type 'int\\\[n]' declared with mismatched bound argument 1" }
+void f2iaf_1 (int,   int[f (0)]);
+void f2iaf_1 (int m, int[m]);     // { dg-warning "argument 2 of type 'int\\\[m]' declared with mismatched bound argument 1" }
+void f2iaf_1 (int x, int a[f (0)]) { (void)&x; (void)&a; }
+void f2iaf_1 (int y, int[y]);     // { dg-warning "argument 2 of type 'int\\\[y]' declared with mismatched bound argument 1" }
+
+
+void f3ia1 (int n, int, int[n]);  // { dg-message "previously declared as 'int\\\[n]' with bound argument 1" }
+void f3ia1 (int, int n, int[n]);  // { dg-warning "argument 3 of type 'int\\\[n]' declared with mismatched bound argument 2" }
+void f3ia1 (int n, int, int[n]);
+
+
+extern int g (int);
+
+void f1iaf_g (int[f (1)]);    // { dg-message "previously declared as 'int\\\[f *\\\(1\\\)]'" }
+void f1iaf_g (int[g (1)]);    // { dg-warning "argument 1 of type 'int\\\[g *\\\(1\\\)]' declared with mismatched bound" }
+void f1iaf_g (int[f (1)]);
+
+
+void nrf1iaf_g (int[f (1)]);  // { dg-message "previously declared as 'int\\\[f *\\\(1\\\)]'" }
+__attribute__ ((nonnull))
+void nrf1iaf_g (int[g (1)]);  // { dg-warning "argument 1 of type 'int\\\[g *\\\(1\\\)]' declared with mismatched bound" }
+__attribute__ ((noreturn))
+void nrf1iaf_g (int[f (1)]);
+
+/* Verify that the presence or absence of static with VLA dooesn't cause
+   unwanted warnings.  */
+
+int f2ia1_1 (int n, int [n][n]);            // { sg-message "previously declared as 'int\\\[n]\\\[n]' with bound argument 1" }
+int f2ia1_1 (int n, int[static n][n]);
+int f2ia1_1 (int n, int a[static n][n]) { return sizeof *a; }
+int f2ia1_1 (int n, int[static n + 1][n]);  // { dg-warning "argument 2 of type 'int\\\[n \\\+ 1]\\\[n]' declared with mismatched bound 'n \\\+ 1'" }
+
+int f2ias1_1 (int n, int [static n][n]);    // { dg-message "previously declared as 'int\\\[n]\\\[n]' with bound argument 1" }
+int f2ias1_1 (int n, int[n][n]);
+int f2ias1_1 (int n, int a[++n][n])         // { dg-warning "argument 2 of type 'int\\\[\\\+\\\+n]\\\[n]' declared with mismatched bound ' ?\\+\\+n'" }
+{ return sizeof *a; }
diff --git a/gcc/tree-pretty-print.c b/gcc/tree-pretty-print.c
index 655061c174d..ad3dc59bcfe 100644
--- a/gcc/tree-pretty-print.c
+++ b/gcc/tree-pretty-print.c
@@ -1682,9 +1682,9 @@ dump_generic_node (pretty_printer *pp, tree node, int spc, dump_flags_t flags,
 	  pp_string (pp, "atomic ");
 	if (quals & TYPE_QUAL_CONST)
 	  pp_string (pp, "const ");
-	else if (quals & TYPE_QUAL_VOLATILE)
+	if (quals & TYPE_QUAL_VOLATILE)
 	  pp_string (pp, "volatile ");
-	else if (quals & TYPE_QUAL_RESTRICT)
+	if (quals & TYPE_QUAL_RESTRICT)
 	  pp_string (pp, "restrict ");
 
 	if (!ADDR_SPACE_GENERIC_P (TYPE_ADDR_SPACE (node)))

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH 2/5] C front end support to detect out-of-bounds accesses to array parameters
  2020-08-13 23:04         ` Martin Sebor
@ 2020-08-17 22:09           ` Joseph Myers
  2020-08-19 22:56             ` Martin Sebor
  0 siblings, 1 reply; 33+ messages in thread
From: Joseph Myers @ 2020-08-17 22:09 UTC (permalink / raw)
  To: Martin Sebor; +Cc: gcc-patches

On Thu, 13 Aug 2020, Martin Sebor via Gcc-patches wrote:

> > * Maybe cdk_pointer is followed by cdk_attrs before cdk_id.  In this case
> > the code won't return.
> 
> I think I see the problem you're pointing out (I just don't see how
> to trigger it or test that it doesn't happen).  If the tweak in
> the attached update doesn't fix it a test case would be helpful.

I think you need a while loop there, not just an if, to account for the 
case of multiple consecutive cdk_attrs.  At least the GNU attribute syntax

   direct-declarator:
[...]
     ( gnu-attributes[opt] declarator )

should produce multiple consecutive cdk_attrs for each level of 
parentheses with attributes inside.

> > * Maybe the code is correct to continue because we're in the case of an
> > array of pointers (cdk_array follows).  But as I understand it, the intent
> > is to set up an "arg spec" that describes only the (multidimensional)
> > array that is the parameter itself - not any array pointed to.  And it
> > looks to me like, in the case of an array of pointers to arrays, both sets
> > of array bounds would end up in the spec constructed.
> 
> Ideally, I'd like to check even pointers to arrays and so they should
> be recorded somewhere.  The middle end code doesn't do any checking
> of those yet for out-of-bounds accesses.  It wasn't a goal for
> the first iteration so I've tweaked the code to avoid recording them.

Could you expand the comment on get_parm_array_spec to specify exactly 
what you think the function should be putting in the returned attribute, 
in what order, in cases where there are array declarators (constant, 
empty, [*] and VLA) intermixed with other kinds of declarators and the 
type from the type specifiers may or may not be an array type itself?  
That will provide a basis for subsequent rounds of review of whether the 
function is actually behaving as expected.

As far as I can see, the logic

+      if (TREE_CODE (nelts) == INTEGER_CST)
+       {
+         /* Skip all constant bounds except the most significant one.
+            The interior ones are included in the array type.  */
+         if (next && (next->kind == cdk_array || next->kind == cdk_pointer))
+           continue;

will skip constant bounds in an array that's the target of a pointer 
declarator, but not any other kind of bounds.  Is that what you intend - 
that all the other kind of bounds in pointed-to arrays will be recorded in 
this string?

> > Then, the code
> > 
> > +      if (pd->kind == cdk_id)
> > +       {
> > +         /* Extract the upper bound from a parameter of an array type.  */
> > 
> > also seems misplaced.  If the type specifiers for the parameter are a
> > typedef for an array type, that array type should be processed *before*
> > the declarator to get the correct semantics (as if the bounds from those
> > type specifiers were given in the declarator), not at the end which gets
> > that type out of order with respect to array declarators.  (Processing
> > before the declarator also means clearing the results of that processing
> > if a pointer declarator is encountered at any point, because in that case
> > the array type in the type specifiers is irrelevant.)
> 
> I'm not sure I follow you here.  Can you show me what you mean on
> a piece of code?  This test case (which IIUC does what you described)
> works as expected:
> 
> $ cat q.c && gcc -O2 -S -Wall q.c
> typedef int A[7][9];
> 
> void f (A[3][5]);

So this is equivalent to A[3][5][7][9].  The c_declarator structures have 
the one for the [3] (the top-level bound) inside the one for the [5].  
The [5] bound is skipped by the "Skip all constant bounds except the most 
significant one." logic.  When the [3] bound is reached, the "break;" at 
the end of that processing means the "Extract the upper bound from a 
parameter of an array type." never gets executed.  Try replacing the [3] 
bound by a VLA bound.  As I read the code, it will end up generating a 
spec string that records first the VLA, then the [7], when it should be 
first the 9 (skipped), then the 7 (skipped), then the 5 (skipped), then 
the VLA.  Or if it's "void f (A *[variable][5]);", it will do the same 
thing (VLA, then 7, although both the 7 and the 9 are part of the 
pointed-to type).

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH 2/5] C front end support to detect out-of-bounds accesses to array parameters
  2020-08-17 22:09           ` Joseph Myers
@ 2020-08-19 22:56             ` Martin Sebor
  2020-08-20  0:09               ` Joseph Myers
  0 siblings, 1 reply; 33+ messages in thread
From: Martin Sebor @ 2020-08-19 22:56 UTC (permalink / raw)
  To: Joseph Myers; +Cc: gcc-patches

[-- Attachment #1: Type: text/plain, Size: 6009 bytes --]

On 8/17/20 4:09 PM, Joseph Myers wrote:
> On Thu, 13 Aug 2020, Martin Sebor via Gcc-patches wrote:
> 
>>> * Maybe cdk_pointer is followed by cdk_attrs before cdk_id.  In this case
>>> the code won't return.
>>
>> I think I see the problem you're pointing out (I just don't see how
>> to trigger it or test that it doesn't happen).  If the tweak in
>> the attached update doesn't fix it a test case would be helpful.
> 
> I think you need a while loop there, not just an if, to account for the
> case of multiple consecutive cdk_attrs.  At least the GNU attribute syntax
> 
>     direct-declarator:
> [...]
>       ( gnu-attributes[opt] declarator )
> 
> should produce multiple consecutive cdk_attrs for each level of
> parentheses with attributes inside.

I had considered a loop but couldn't find a way to trigger what you
describe (or a test in the testsuite that would do it) so I didn't
use one.  I saw loops like that in other places but I couldn't get
even those to uncover such a test case.  Here's what I tried:

   #define A(N) __attribute__ ((aligned (N), may_alias))
   int n;
   void f (int (* A (2) A (4) (* A (2) A (4) (* A (2) A (4) [n])[n])));

Sequences of consecutive attributes are all chained together.

I've added the loop here but I have no test for it.  It would be
good to add one if it really is needed.

> 
>>> * Maybe the code is correct to continue because we're in the case of an
>>> array of pointers (cdk_array follows).  But as I understand it, the intent
>>> is to set up an "arg spec" that describes only the (multidimensional)
>>> array that is the parameter itself - not any array pointed to.  And it
>>> looks to me like, in the case of an array of pointers to arrays, both sets
>>> of array bounds would end up in the spec constructed.
>>
>> Ideally, I'd like to check even pointers to arrays and so they should
>> be recorded somewhere.  The middle end code doesn't do any checking
>> of those yet for out-of-bounds accesses.  It wasn't a goal for
>> the first iteration so I've tweaked the code to avoid recording them.
> 
> Could you expand the comment on get_parm_array_spec to specify exactly
> what you think the function should be putting in the returned attribute,
> in what order, in cases where there are array declarators (constant,
> empty, [*] and VLA) intermixed with other kinds of declarators and the
> type from the type specifiers may or may not be an array type itself?
> That will provide a basis for subsequent rounds of review of whether the
> function is actually behaving as expected.

Done.

> 
> As far as I can see, the logic
> 
> +      if (TREE_CODE (nelts) == INTEGER_CST)
> +       {
> +         /* Skip all constant bounds except the most significant one.
> +            The interior ones are included in the array type.  */
> +         if (next && (next->kind == cdk_array || next->kind == cdk_pointer))
> +           continue;
> 
> will skip constant bounds in an array that's the target of a pointer
> declarator, but not any other kind of bounds.  Is that what you intend -
> that all the other kind of bounds in pointed-to arrays will be recorded in
> this string?

The immediate goal (for the front end) is to detect differences
in the form ([] vs [N]) or value of the most significant bound
in parameters of array types (before they decay to pointers),
and differences in the form ([*] vs [n]) or (the presumed) value
(e.g., [n] vs [n + 1]) of VLA bounds.

So I need to encode every variable bound (specified or unspecified).
For ordinary arrays I want to encode just the form of the most
significant bound.

> 
>>> Then, the code
>>>
>>> +      if (pd->kind == cdk_id)
>>> +       {
>>> +         /* Extract the upper bound from a parameter of an array type.  */
>>>
>>> also seems misplaced.  If the type specifiers for the parameter are a
>>> typedef for an array type, that array type should be processed *before*
>>> the declarator to get the correct semantics (as if the bounds from those
>>> type specifiers were given in the declarator), not at the end which gets
>>> that type out of order with respect to array declarators.  (Processing
>>> before the declarator also means clearing the results of that processing
>>> if a pointer declarator is encountered at any point, because in that case
>>> the array type in the type specifiers is irrelevant.)
>>
>> I'm not sure I follow you here.  Can you show me what you mean on
>> a piece of code?  This test case (which IIUC does what you described)
>> works as expected:
>>
>> $ cat q.c && gcc -O2 -S -Wall q.c
>> typedef int A[7][9];
>>
>> void f (A[3][5]);
> 
> So this is equivalent to A[3][5][7][9].  The c_declarator structures have
> the one for the [3] (the top-level bound) inside the one for the [5].
> The [5] bound is skipped by the "Skip all constant bounds except the most
> significant one." logic.  When the [3] bound is reached, the "break;" at
> the end of that processing means the "Extract the upper bound from a
> parameter of an array type." never gets executed.  Try replacing the [3]
> bound by a VLA bound.  As I read the code, it will end up generating a
> spec string that records first the VLA, then the [7], when it should be
> first the 9 (skipped), then the 7 (skipped), then the 5 (skipped), then
> the VLA.  Or if it's "void f (A *[variable][5]);", it will do the same
> thing (VLA, then 7, although both the 7 and the 9 are part of the
> pointed-to type).

Replacing the constant bound with a variable one helped, thanks!
Attached is another update.  While working on it and making sure
the function avoids pointers to arrays I decided to add another
one that at least checks them for mismatches (they're ignored
otherwise).  It helped me get a better handle on these obscure
things.  I can't imagine anyone actually declaring a function
that takes a pointer to an array of pointers to arrays (I can't
even read it) but it was instructive nonetheless and helped me
find some of the problems you were pointing out that I couldn't
see.

Martin

[-- Attachment #2: gcc-50584-2.diff --]
[-- Type: text/x-patch, Size: 77621 bytes --]

[2/5] - C front end support to detect out-of-bounds accesses to array parameters.

gcc/c-family/ChangeLog:

	PR c/50584
	* c-common.h (warn_parm_array_mismatch): Declare new function.
	(has_attribute): Move declaration of an existing function.
	(build_attr_access_from_parms): Declare new function.
	* c-warn.c (parm_array_as_string): Define new function.
	(plus_one):  Define new function.
	(warn_parm_ptrarray_mismatch): Define new function.
	(warn_parm_array_mismatch):  Define new function.
	(vla_bound_parm_decl): New function.
	* c.opt (-Warray-parameter, -Wvla-parameter): New options.
	* c-pretty-print.c (pp_c_type_qualifier_list): Don't print array type
	qualifiers here...
	(c_pretty_printer::direct_abstract_declarator): ...but instead print
	them in brackets here.  Also print [static].  Strip extraneous
	expressions from VLA bounds.

gcc/c/ChangeLog:

	PR c/50584
	* c-decl.c (lookup_last_decl): Define new function.
	(c_decl_attributes): Call it.
	(start_decl): Add argument and use it.
	(finish_decl): Call build_attr_access_from_parms and decl_attributes.
	(get_parm_array_spec): Define new function.
	(push_parm_decl): Call get_parm_array_spec.
	(start_function): Call warn_parm_array_mismatch.  Build attribute
	access and add it to current function.
	* c-parser.c (c_parser_declaration_or_fndef): Diagnose mismatches
	in forms of array parameters.
	* c-tree.h (start_decl): Add argument.

gcc/ChangeLog:

	PR c/50584
	* calls.c (initialize_argument_information): Remove assertion.
	* doc/invoke.texi (-Warray-parameter, -Wvla-parameter): Document.
	* tree-pretty-print.c (dump_generic_node): Correct handling of
	qualifiers.

gcc/testsuite/ChangeLog:

	PR c/50584
	* c-c++-common/Warray-bounds-6.c: Correct C++ declaration, adjust
	text of expected diagnostics.
	* gcc.dg/Wbuiltin-declaration-mismatch-9.c: Prune expected warning.
	* gcc.dg/Warray-parameter-2.c: New test.
	* gcc.dg/Warray-parameter-3.c: New test.
	* gcc.dg/Warray-parameter-4.c: New test.
	* gcc.dg/Warray-parameter-5.c: New test.
	* gcc.dg/Warray-parameter.c: New test.
	* gcc.dg/Wvla-parameter-2.c: New test.
	* gcc.dg/Wvla-parameter-3.c: New test.
	* gcc.dg/Wvla-parameter.c: New test.

diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index 4fc64bc4aa6..a61b25f95f1 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -1321,6 +1321,7 @@ extern void c_do_switch_warnings (splay_tree, location_t, tree, tree, bool);
 extern void warn_for_omitted_condop (location_t, tree);
 extern bool warn_for_restrict (unsigned, tree *, unsigned);
 extern void warn_for_address_or_pointer_of_packed_member (tree, tree);
+extern void warn_parm_array_mismatch (location_t, tree, tree);
 
 /* Places where an lvalue, or modifiable lvalue, may be required.
    Used to select diagnostic messages in lvalue_error and
@@ -1375,6 +1376,8 @@ extern tree find_tm_attribute (tree);
 extern const struct attribute_spec::exclusions attr_cold_hot_exclusions[];
 extern const struct attribute_spec::exclusions attr_noreturn_exclusions[];
 extern tree handle_noreturn_attribute (tree *, tree, tree, int, bool *);
+extern bool has_attribute (location_t, tree, tree, tree (*)(tree));
+extern tree build_attr_access_from_parms (tree, bool);
 
 /* In c-format.c.  */
 extern bool valid_format_string_type_p (tree);
@@ -1403,8 +1406,6 @@ extern void maybe_suggest_missing_token_insertion (rich_location *richloc,
 						   location_t prev_token_loc);
 extern tree braced_lists_to_strings (tree, tree);
 
-extern bool has_attribute (location_t, tree, tree, tree (*)(tree));
-
 #if CHECKING_P
 namespace selftest {
   /* Declarations for specific families of tests within c-family,
diff --git a/gcc/c-family/c-pretty-print.c b/gcc/c-family/c-pretty-print.c
index ec0bafe1010..03c89385386 100644
--- a/gcc/c-family/c-pretty-print.c
+++ b/gcc/c-family/c-pretty-print.c
@@ -248,9 +248,12 @@ pp_c_type_qualifier_list (c_pretty_printer *pp, tree t)
   if (!TYPE_P (t))
     t = TREE_TYPE (t);
 
-  qualifiers = TYPE_QUALS (t);
-  pp_c_cv_qualifiers (pp, qualifiers,
-		      TREE_CODE (t) == FUNCTION_TYPE);
+  if (TREE_CODE (t) != ARRAY_TYPE)
+    {
+      qualifiers = TYPE_QUALS (t);
+      pp_c_cv_qualifiers (pp, qualifiers,
+			  TREE_CODE (t) == FUNCTION_TYPE);
+    }
 
   if (!ADDR_SPACE_GENERIC_P (TYPE_ADDR_SPACE (t)))
     {
@@ -572,6 +575,8 @@ c_pretty_printer::abstract_declarator (tree t)
 void
 c_pretty_printer::direct_abstract_declarator (tree t)
 {
+  bool add_space = false;
+
   switch (TREE_CODE (t))
     {
     case POINTER_TYPE:
@@ -585,17 +590,65 @@ c_pretty_printer::direct_abstract_declarator (tree t)
 
     case ARRAY_TYPE:
       pp_c_left_bracket (this);
+
+      if (int quals = TYPE_QUALS (t))
+	{
+	  /* Print the array qualifiers such as in "T[const restrict 3]".  */
+	  pp_c_cv_qualifiers (this, quals, false);
+	  add_space = true;
+	}
+
+      if (tree arr = lookup_attribute ("array", TYPE_ATTRIBUTES (t)))
+	{
+	  if (TREE_VALUE (arr))
+	    {
+	      /* Print the specifier as in "T[static 3]" that's not actually
+		 part of the type but may be added by the front end.  */
+	      pp_c_ws_string (this, "static");
+	      add_space = true;
+	    }
+	  else if (!TYPE_DOMAIN (t))
+	    /* For arrays of unspecified bound using the [*] notation. */
+	    pp_character (this, '*');
+	}
+
       if (tree dom = TYPE_DOMAIN (t))
 	{
 	  if (tree maxval = TYPE_MAX_VALUE (dom))
 	    {
+	      if (add_space)
+		pp_space (this);
+
 	      tree type = TREE_TYPE (maxval);
 
 	      if (tree_fits_shwi_p (maxval))
 		pp_wide_integer (this, tree_to_shwi (maxval) + 1);
-	      else
+	      else if (TREE_CODE (maxval) == INTEGER_CST)
 		expression (fold_build2 (PLUS_EXPR, type, maxval,
 					 build_int_cst (type, 1)));
+	      else
+		{
+		  /* Strip the expressions from around a VLA bound added
+		     internally to make it fit the domain mold, including
+		     any casts.  */
+		  if (TREE_CODE (maxval) == NOP_EXPR)
+		    maxval = TREE_OPERAND (maxval, 0);
+		  if (TREE_CODE (maxval) == PLUS_EXPR
+		      && integer_all_onesp (TREE_OPERAND (maxval, 1)))
+		    {
+		      maxval = TREE_OPERAND (maxval, 0);
+		      if (TREE_CODE (maxval) == NOP_EXPR)
+			maxval = TREE_OPERAND (maxval, 0);
+		    }
+		  if (TREE_CODE (maxval) == SAVE_EXPR)
+		    {
+		      maxval = TREE_OPERAND (maxval, 0);
+		      if (TREE_CODE (maxval) == NOP_EXPR)
+			maxval = TREE_OPERAND (maxval, 0);
+		    }
+
+		  expression (maxval);
+		}
 	    }
 	  else if (TYPE_SIZE (t))
 	    /* Print zero for zero-length arrays but not for flexible
diff --git a/gcc/c-family/c-warn.c b/gcc/c-family/c-warn.c
index fea8885bf35..7e9ecaeb591 100644
--- a/gcc/c-family/c-warn.c
+++ b/gcc/c-family/c-warn.c
@@ -3111,6 +3111,200 @@ plus_one (tree expr)
   return fold_build2 (PLUS_EXPR, type, expr, build_int_cst (type, 1));
 }
 
+/* Try to strip the expressions from around a VLA bound added internally
+   to make it fit the domain mold, including any casts, and return
+   the result.  The goal is to obtain the PARM_DECL the VLA bound may
+   refer to.  */
+
+static tree
+vla_bound_parm_decl (tree expr)
+{
+  if (!expr)
+    return NULL_TREE;
+
+  if (TREE_CODE (expr) == NOP_EXPR)
+    expr = TREE_OPERAND (expr, 0);
+  if (TREE_CODE (expr) == PLUS_EXPR
+      && integer_all_onesp (TREE_OPERAND (expr, 1)))
+    {
+      expr = TREE_OPERAND (expr, 0);
+      if (TREE_CODE (expr) == NOP_EXPR)
+	expr = TREE_OPERAND (expr, 0);
+    }
+  if (TREE_CODE (expr) == SAVE_EXPR)
+    {
+      expr = TREE_OPERAND (expr, 0);
+      if (TREE_CODE (expr) == NOP_EXPR)
+	expr = TREE_OPERAND (expr, 0);
+    }
+  return expr;
+}
+
+/* Diagnose mismatches in VLA bounds between function parameters NEWPARMS
+   of pointer types on a redeclaration os a function previously declared
+   with CURPARMS at ORIGLOC.  */
+
+static void
+warn_parm_ptrarray_mismatch (location_t origloc, tree curparms, tree newparms)
+{
+  /* Maps each named integral parameter seen so far to its position
+     in the argument list; used to associate VLA sizes with arguments.  */
+  hash_map<tree, unsigned> curparm2pos;
+  hash_map<tree, unsigned> newparm2pos;
+
+  unsigned parmpos = 1;
+  for (tree curp = curparms, newp = newparms; curp && newp;
+       curp = TREE_CHAIN (curp), newp = TREE_CHAIN (newp), ++parmpos)
+    {
+      tree curtyp = TREE_TYPE (curp), newtyp = TREE_TYPE (newp);
+      if (INTEGRAL_TYPE_P (curtyp))
+	{
+	  /* Only add named parameters; unnamed ones cannot be referred
+	     to in VLA bounds.  */
+	  if (DECL_NAME (curp))
+	    curparm2pos.put (curp, parmpos);
+	  if (DECL_NAME (newp))
+	    newparm2pos.put (newp, parmpos);
+
+	  continue;
+	}
+
+      /* The parameter types should match at this point so only test one.  */
+      if (TREE_CODE (curtyp) != POINTER_TYPE)
+	continue;
+
+      do
+	{
+	  curtyp = TREE_TYPE (curtyp);
+	  newtyp = TREE_TYPE (newtyp);
+	}
+      while (TREE_CODE (curtyp) == POINTER_TYPE
+	     && TREE_CODE (newtyp) == POINTER_TYPE);
+
+      if (TREE_CODE (curtyp) != ARRAY_TYPE
+	  || TREE_CODE (newtyp) != ARRAY_TYPE)
+	{
+	  if (curtyp == error_mark_node
+	      || newtyp == error_mark_node)
+	    return;
+
+	  continue;
+	}
+
+      tree curdom = TYPE_DOMAIN (curtyp), newdom = TYPE_DOMAIN (newtyp);
+      tree curbnd = curdom ? TYPE_MAX_VALUE (curdom) : NULL_TREE;
+      tree newbnd = newdom ? TYPE_MAX_VALUE (newdom) : NULL_TREE;
+
+      if (DECL_P (curp))
+	origloc = DECL_SOURCE_LOCATION (curp);
+      else if (EXPR_P (curp) && EXPR_HAS_LOCATION (curp))
+	origloc = EXPR_LOCATION (curp);
+
+      /* The location of the parameter in the current redeclaration.  */
+      location_t newloc = DECL_SOURCE_LOCATION (newp);
+      if (origloc == UNKNOWN_LOCATION)
+	origloc = newloc;
+
+      /* Issue -Warray-parameter onless one or more mismatches involves
+	 a VLA bound; then issue -Wvla-parameter.  */
+      int opt = OPT_Warray_parameter_;
+      /* Traverse the two array types looking for variable bounds and
+	 comparing the two in each pair for mismatches either in their
+	 positions in the function parameter list or lexicographically
+	 for others.  Record the 1-based parameter position of each
+	 mismatch in BNDVEC, and the location of each parameter in
+	 the mismatch in WARNLOC (for the new parameter list) and
+	 NOTELOC (for the current parameter list).  */
+      unsigned bndpos = 1;
+      auto_vec<unsigned> bndvec;
+      gcc_rich_location warnloc (newloc);
+      gcc_rich_location noteloc (origloc);
+      for ( ; curtyp || newtyp;
+	    ++bndpos,
+	      curbnd = curdom ? TYPE_MAX_VALUE (curdom) : NULL_TREE,
+	      newbnd = newdom ? TYPE_MAX_VALUE (newdom) : NULL_TREE)
+	{
+	  /* Try to strip each bound down to the PARM_DECL if it does
+	     correspond to one.  Either bound can be null if it's
+	     unspecified (i.e., has the [*] form).  */
+	  curbnd = vla_bound_parm_decl (curbnd);
+	  newbnd = vla_bound_parm_decl (newbnd);
+
+	  /* Peel the current bound off CURTYP and NEWTYP, skipping
+	     over any subsequent pointer types.  */
+	  if (curtyp && TREE_CODE (curtyp) == ARRAY_TYPE)
+	    {
+	      do
+		curtyp = TREE_TYPE (curtyp);
+	      while (TREE_CODE (curtyp) == POINTER_TYPE);
+	      if (TREE_CODE (curtyp) == ARRAY_TYPE)
+		curdom = TYPE_DOMAIN (curtyp);
+	      else
+		curdom = NULL_TREE;
+	    }
+	  else
+	    curtyp = NULL_TREE;
+
+	  if (newtyp && TREE_CODE (newtyp) == ARRAY_TYPE)
+	    {
+	      do
+		newtyp = TREE_TYPE (newtyp);
+	      while (TREE_CODE (newtyp) == POINTER_TYPE);
+	      if (TREE_CODE (newtyp) == ARRAY_TYPE)
+		newdom = TYPE_DOMAIN (newtyp);
+	      else
+		newdom = NULL_TREE;
+	    }
+	  else
+	    newtyp = NULL_TREE;
+
+	  /* Move on to the next bound if this one is unspecified.  */
+	  if (!curbnd && !newbnd)
+	    continue;
+
+	  /* Try to find each bound in the parameter list.  */
+	  const unsigned* const pcurbndpos = curparm2pos.get (curbnd);
+	  const unsigned* const pnewbndpos = newparm2pos.get (newbnd);
+	  /* Move on if both bounds refer to the same parameter.  */
+	  if (pcurbndpos && pnewbndpos && *pcurbndpos == *pnewbndpos)
+	    continue;
+
+	  /* Move on if the bounds look the same.  */
+	  if (!pcurbndpos && !pnewbndpos
+	      && curbnd && newbnd
+	      && operand_equal_p (curbnd, newbnd, OEP_LEXICOGRAPHIC))
+	    continue;
+
+	  if ((curbnd && TREE_CODE (curbnd) != INTEGER_CST)
+	      || (newbnd && TREE_CODE (newbnd) != INTEGER_CST))
+	    opt = OPT_Wvla_parameter;
+
+	  /* Record the mismatch.  */
+	  bndvec.safe_push (bndpos);
+	  /* Underline the bounding parameter in the declaration.  */
+	  if (curbnd && TREE_CODE (curbnd) == PARM_DECL)
+	    noteloc.add_range (DECL_SOURCE_LOCATION (curbnd));
+	  if (newbnd && TREE_CODE (newbnd) == PARM_DECL)
+	    warnloc.add_range (DECL_SOURCE_LOCATION (newbnd));
+	}
+
+      const unsigned nbnds = bndvec.length ();
+      if (!nbnds)
+	continue;
+
+      /* Use attr_access to format the parameter types.  */
+      attr_access spec = { };
+      const std::string newparmstr = spec.array_as_string (TREE_TYPE (newp));
+      const std::string curparmstr = spec.array_as_string (TREE_TYPE (curp));
+
+      if (warning_n (&warnloc, opt, nbnds,
+		     "mismatch in bound %Z of argument %u declared as %qs",
+		     "mismatch in bounds %Z of argument %u declared as %qs",
+		     bndvec.address (), nbnds, parmpos, newparmstr.c_str ()))
+	inform (&noteloc, "previously declared as %qs",	curparmstr.c_str ());
+    }
+}
+
 /* Detect and diagnose a mismatch between an attribute access specification
    on the original declaration of FNDECL and that on the parameters NEWPARMS
    from its refeclaration.  ORIGLOC is the location of the first declaration
@@ -3146,11 +3340,15 @@ warn_parm_array_mismatch (location_t origloc, tree fndecl, tree newparms)
   rdwr_map new_idx;
   init_attr_rdwr_indices (&new_idx, newattrs);
 
-  /* If both are empty there's nothing to do.  If at least one isn't
-     empty there may be mismatches, such as between f(T*) and f(T[1]),
-     where the former mapping woud be empty.  */
   if (cur_idx.is_empty () && new_idx.is_empty ())
-    return;
+    {
+      /* If both specs are empty check pointers to VLAs for mismatches. */
+      warn_parm_ptrarray_mismatch (origloc, curparms, newparms);
+      return;
+    }
+  /* ...otherwise, if at least one spec isn't empty there may be mismatches,
+     such as  between f(T*) and f(T[1]), where the former mapping woud be
+     empty.  */
 
   /* Create an empty access specification and use it for pointers with
      no spec of their own.  */
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 2b1aca16eb4..6f3997405a1 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -338,6 +338,14 @@ Warray-bounds=
 LangEnabledBy(C ObjC C++ LTO ObjC++,Wall,1,0)
 ; in common.opt
 
+Warray-parameter
+C ObjC C++ ObjC++ Warning Alias(Warray-parameter=, 2, 0)
+Warn about mismatched declarations of array parameters and unsafe accesses to them.
+
+Warray-parameter=
+C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_array_parameter) IntegerRange(0, 2) LangEnabledBy(C ObjC C++ ObjC++,Wall, 2, 0) Warning
+Warn about mismatched declarations of array parameters and unsafe accesses to them.
+
 Wzero-length-bounds
 C ObjC C++ ObjC++ Var(warn_zero_length_bounds) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall)
 Warn about accesses to interior zero-length array members.
@@ -1253,6 +1261,10 @@ Wno-vla-larger-than
 C ObjC C++ LTO ObjC++ Alias(Wvla-larger-than=,18446744073709551615EiB,none) Warning
 Disable Wvla-larger-than= warning.  Equivalent to Wvla-larger-than=<SIZE_MAX> or larger.
 
+Wvla-parameter
+C ObjC C++ ObjC++ Var(warn_vla_parameter) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall)
+Warn about mismatched declarations of VLA parameters.
+
 Wvolatile
 C++ ObjC++ Var(warn_volatile) Warning
 Warn about deprecated uses of volatile qualifier.
diff --git a/gcc/c/c-decl.c b/gcc/c/c-decl.c
index 5d6b504fe78..1402e8b3c93 100644
--- a/gcc/c/c-decl.c
+++ b/gcc/c/c-decl.c
@@ -25,6 +25,7 @@ along with GCC; see the file COPYING3.  If not see
    line numbers.  For example, the CONST_DECLs for enum values.  */
 
 #include "config.h"
+#define INCLUDE_STRING
 #define INCLUDE_UNIQUE_PTR
 #include "system.h"
 #include "coretypes.h"
@@ -58,6 +59,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "c-family/known-headers.h"
 #include "c-family/c-spellcheck.h"
 
+#include "tree-pretty-print.h"
+
 /* In grokdeclarator, distinguish syntactic contexts of declarators.  */
 enum decl_context
 { NORMAL,			/* Ordinary declaration */
@@ -4967,6 +4970,17 @@ groktypename (struct c_type_name *type_name, tree *expr,
   return type;
 }
 
+/* Looks up the most recent pushed declaration corresponding to DECL.  */
+
+static tree
+lookup_last_decl (tree decl)
+{
+  tree last_decl = lookup_name (DECL_NAME (decl));
+  if (!last_decl)
+    last_decl = lookup_name_in_scope (DECL_NAME (decl), external_scope);
+  return last_decl;
+}
+
 /* Wrapper for decl_attributes that adds some implicit attributes
    to VAR_DECLs or FUNCTION_DECLs.  */
 
@@ -4995,10 +5009,7 @@ c_decl_attributes (tree *node, tree attributes, int flags)
      so far so that attributes on the current declaration that's
      about to be pushed that conflict with the former can be detected,
      diagnosed, and rejected as appropriate.  */
-  tree last_decl = lookup_name (DECL_NAME (*node));
-  if (!last_decl)
-    last_decl = lookup_name_in_scope (DECL_NAME (*node), external_scope);
-
+  tree last_decl = lookup_last_decl (*node);
   return decl_attributes (node, attributes, flags, last_decl);
 }
 
@@ -5008,6 +5019,8 @@ c_decl_attributes (tree *node, tree attributes, int flags)
    have been parsed, before parsing the initializer if any.
    Here we create the ..._DECL node, fill in its type,
    and put it on the list of decls for the current context.
+   When nonnull, set *LASTLOC to the location of the prior declaration
+   of the same entity if one exists.
    The ..._DECL node is returned as the value.
 
    Exception: for arrays where the length is not specified,
@@ -5020,7 +5033,7 @@ c_decl_attributes (tree *node, tree attributes, int flags)
 
 tree
 start_decl (struct c_declarator *declarator, struct c_declspecs *declspecs,
-	    bool initialized, tree attributes)
+	    bool initialized, tree attributes, location_t *lastloc /* = NULL */)
 {
   tree decl;
   tree tem;
@@ -5038,6 +5051,10 @@ start_decl (struct c_declarator *declarator, struct c_declspecs *declspecs,
   if (!decl || decl == error_mark_node)
     return NULL_TREE;
 
+  if (tree lastdecl = lastloc ? lookup_last_decl (decl) : NULL_TREE)
+    if (lastdecl != error_mark_node)
+      *lastloc = DECL_SOURCE_LOCATION (lastdecl);
+
   if (expr)
     add_stmt (fold_convert (void_type_node, expr));
 
@@ -5475,6 +5492,14 @@ finish_decl (tree decl, location_t init_loc, tree init,
 	  if (asmspec && VAR_P (decl) && C_DECL_REGISTER (decl))
 	    DECL_HARD_REGISTER (decl) = 1;
 	  rest_of_decl_compilation (decl, true, 0);
+
+	  if (TREE_CODE (decl) == FUNCTION_DECL)
+	    {
+	      tree parms = DECL_ARGUMENTS (decl);
+	      const bool builtin = fndecl_built_in_p (decl);
+	      if (tree access = build_attr_access_from_parms (parms, !builtin))
+		decl_attributes (&decl, access, 0);
+	    }
 	}
       else
 	{
@@ -5627,6 +5652,133 @@ grokparm (const struct c_parm *parm, tree *expr)
   return decl;
 }
 
+/* Return attribute "arg spec" corresponding to an array/VLA parameter
+   described by PARM, concatenated onto attributes ATTRS.
+   The spec consists of one dollar symbol for each specified variable
+   bound, one asterisk for each unspecified variable bound, followed
+   by at most one specification of the most significant bound of
+   an ordinary array parameter.  For ordinary arrays the specification
+   is either the constant bound itself, or the space character for
+   an array with an unspecified bound (the [] form).  Finally, a chain
+   of specified variable bounds is appended to the spec, starting with
+   the most significant bound.  For example, the PARM T a[2][m][3][n]
+   will produce __attribute__((arg spec ("[$$2]", m, n)).
+   No "arg spec"  is created for parameters of pointer types, making
+   a distinction between T(*)[N] (or, equivalently, T[][N]) and
+   the T[M][N] form, all of which have the same type and are represented
+   the same, but only the last of which gets an "arg spec" describing
+   the most significant bound M.  */
+
+static tree
+get_parm_array_spec (const struct c_parm *parm, tree attrs)
+{
+  std::string spec;
+
+  /* A list of VLA variable bounds or null if not specified.  */
+  tree vbchain = NULL_TREE;
+
+  /* Create a string representation for the bounds of the array/VLA.  */
+  for (c_declarator *pd = parm->declarator, *next; pd; pd = next)
+    {
+      next = pd->declarator;
+      while (next && next->kind == cdk_attrs)
+	next = next->declarator;
+
+      if ((pd->kind == cdk_pointer || pd->kind == cdk_function)
+	  && (!next || next->kind == cdk_id))
+	{
+	  /* Do nothing for the common case of a pointer.  The fact that
+	     the parameter is one can be deduced from the absence of
+	     an arg spec for it.  */
+	  return attrs;
+	}
+
+      if (pd->kind == cdk_id)
+	{
+	  if (vbchain)
+	    continue;
+
+	  /* Extract the upper bound from a parameter of an array type
+	     unless the parameter is an ordinary array of unspecified
+	     bound in which case a next iteration of the loop will exit.  */
+	  if (parm->specs->type
+	      && TREE_CODE (parm->specs->type) == ARRAY_TYPE
+	      && TYPE_DOMAIN (parm->specs->type)
+	      && TYPE_MAX_VALUE (TYPE_DOMAIN (parm->specs->type))
+	      && (spec.empty () || spec.end ()[-1] != ' '))
+	    {
+	      tree max = TYPE_MAX_VALUE (TYPE_DOMAIN (parm->specs->type));
+	      if (tree_fits_shwi_p (max))
+		{
+		  /* The upper bound is the value of the largest valid index.  */
+		  HOST_WIDE_INT n = tree_to_shwi (max) + 1;
+		  char buf[40];
+		  sprintf (buf, "%lu", (unsigned long)n);
+		  spec += buf;
+		}
+	    }
+	  continue;
+	}
+
+      if (pd->kind != cdk_array)
+	continue;
+
+      if (pd->u.array.vla_unspec_p)
+	{
+	  /* Each unspecified bound is represented by a star.  There
+	     can be any number of these in a declaration (but none in
+	     a definition).  */
+	  spec += '*';
+	  continue;
+	}
+
+      tree nelts = pd->u.array.dimen;
+      if (!nelts)
+	{
+	  /* Ordinary array of unspecified size is represented by
+	     a space.  There can be at most one for the most significant
+	     bound.  Exit on the next iteration which determines whether
+	     or not PARM is declared as a pointer or an array.  */
+	  spec += ' ';
+	  continue;
+	}
+
+      if (TREE_CODE (nelts) == INTEGER_CST)
+	{
+	  /* Skip all constant bounds except the most significant one.
+	     The interior ones are included in the array type.  */
+	  if (next && (next->kind == cdk_array || next->kind == cdk_pointer))
+	    continue;
+
+	  if (!tree_fits_uhwi_p (nelts))
+	    /* Bail completely on invalid bounds.  */
+	    return attrs;
+
+	  char buf[40];
+	  const char *code = pd->u.array.static_p ? "s" : "";
+	  unsigned HOST_WIDE_INT n = tree_to_uhwi (nelts);
+	  sprintf (buf, "%s%llu", code, (unsigned long long)n);
+	  spec += buf;
+	  break;
+	}
+
+      /* Each variable VLA bound is represented by a dollar sign.  */
+      spec += "$";
+      vbchain = tree_cons (NULL_TREE, nelts, vbchain);
+    }
+
+  if (spec.empty ())
+    return attrs;
+
+  spec.insert (0, "[");
+  spec += ']';
+
+  tree acsstr = build_string (spec.length () + 1, spec.c_str ());
+  tree args = tree_cons (NULL_TREE, acsstr, vbchain);
+  tree name = get_identifier ("arg spec");
+  return tree_cons (name, args, attrs);
+}
+
 /* Given a parsed parameter declaration, decode it into a PARM_DECL
    and push that on the current scope.  EXPR is a pointer to an
    expression that needs to be evaluated for the side effects of array
@@ -5636,12 +5788,12 @@ void
 push_parm_decl (const struct c_parm *parm, tree *expr)
 {
   tree attrs = parm->attrs;
-  tree decl;
-
-  decl = grokdeclarator (parm->declarator, parm->specs, PARM, false, NULL,
-			 &attrs, expr, NULL, DEPRECATED_NORMAL);
+  tree decl = grokdeclarator (parm->declarator, parm->specs, PARM, false, NULL,
+			      &attrs, expr, NULL, DEPRECATED_NORMAL);
   if (decl && DECL_P (decl))
     DECL_SOURCE_LOCATION (decl) = parm->loc;
+
+  attrs = get_parm_array_spec (parm, attrs);
   decl_attributes (&decl, attrs, 0);
 
   decl = pushdecl (decl);
@@ -9227,6 +9379,7 @@ start_function (struct c_declspecs *declspecs, struct c_declarator *declarator,
   old_decl = lookup_name_in_scope (DECL_NAME (decl1), current_scope);
   if (old_decl && TREE_CODE (old_decl) != FUNCTION_DECL)
     old_decl = NULL_TREE;
+
   current_function_prototype_locus = UNKNOWN_LOCATION;
   current_function_prototype_built_in = false;
   current_function_prototype_arg_types = NULL_TREE;
@@ -9357,12 +9510,22 @@ start_function (struct c_declspecs *declspecs, struct c_declarator *declarator,
 		 "%qD is normally a non-static function", decl1);
     }
 
+  tree parms = current_function_arg_info->parms;
+  if (old_decl)
+    {
+      location_t origloc = DECL_SOURCE_LOCATION (old_decl);
+      warn_parm_array_mismatch (origloc, old_decl, parms);
+    }
+
   /* Record the decl so that the function name is defined.
      If we already have a decl for this name, and it is a FUNCTION_DECL,
      use the old decl.  */
 
   current_function_decl = pushdecl (decl1);
 
+  if (tree access = build_attr_access_from_parms (parms, false))
+    decl_attributes (&current_function_decl, access, 0, old_decl);
+
   push_scope ();
   declare_parm_level ();
 
diff --git a/gcc/c/c-parser.c b/gcc/c/c-parser.c
index 7961cbc98bb..f5d18195c14 100644
--- a/gcc/c/c-parser.c
+++ b/gcc/c/c-parser.c
@@ -68,6 +68,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "intl.h"
 #include "c-family/name-hint.h"
 #include "tree-iterator.h"
+#include "tree-pretty-print.h"
 #include "memmodel.h"
 #include "c-family/known-headers.h"
 
@@ -2296,13 +2297,11 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok,
 		  c_parser_skip_to_end_of_block_or_statement (parser);
 		  return;
 		}
-	      tree d = start_decl (declarator, specs, false,
-				   chainon (postfix_attrs,
-					    all_prefix_attrs));
-	      if (d
-		  && TREE_CODE (d) == FUNCTION_DECL
-		  && DECL_ARGUMENTS (d) == NULL_TREE
-		  && DECL_INITIAL (d) == NULL_TREE)
+
+	      location_t lastloc = UNKNOWN_LOCATION;
+	      tree attrs = chainon (postfix_attrs, all_prefix_attrs);
+	      tree d = start_decl (declarator, specs, false, attrs, &lastloc);
+	      if (d && TREE_CODE (d) == FUNCTION_DECL)
 		{
 		  /* Find the innermost declarator that is neither cdk_id
 		     nor cdk_attrs.  */
@@ -2331,10 +2330,18 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok,
 			gcc_unreachable ();
 		      }
 
-		  /* If it exists and is cdk_function, use its parameters.  */
+		  /* If it exists and is cdk_function declaration whose
+		     arguments have not been set yet, use its arguments.  */
 		  if (last_non_id_attrs
 		      && last_non_id_attrs->kind == cdk_function)
-		    DECL_ARGUMENTS (d) = last_non_id_attrs->u.arg_info->parms;
+		    {
+		      tree parms = last_non_id_attrs->u.arg_info->parms;
+		      if (DECL_ARGUMENTS (d) == NULL_TREE
+			  && DECL_INITIAL (d) == NULL_TREE)
+			DECL_ARGUMENTS (d) = parms;
+
+		      warn_parm_array_mismatch (lastloc, d, parms);
+		    }
 		}
 	      if (omp_declare_simd_clauses.exists ())
 		{
@@ -2363,7 +2370,7 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok,
 	      if (d)
 		finish_decl (d, UNKNOWN_LOCATION, NULL_TREE,
 			     NULL_TREE, asm_name);
-	      
+
 	      if (c_parser_next_token_is_keyword (parser, RID_IN))
 		{
 		  if (d)
diff --git a/gcc/c/c-tree.h b/gcc/c/c-tree.h
index 10938cf0857..3adc29dafc4 100644
--- a/gcc/c/c-tree.h
+++ b/gcc/c/c-tree.h
@@ -609,7 +609,7 @@ extern void shadow_tag_warned (const struct c_declspecs *, int);
 extern tree start_enum (location_t, struct c_enum_contents *, tree);
 extern bool start_function (struct c_declspecs *, struct c_declarator *, tree);
 extern tree start_decl (struct c_declarator *, struct c_declspecs *, bool,
-			tree);
+			tree, location_t * = NULL);
 extern tree start_struct (location_t, enum tree_code, tree,
 			  class c_struct_parse_info **);
 extern void store_parm_decls (void);
diff --git a/gcc/calls.c b/gcc/calls.c
index 77ab8647a10..79aa2c09b29 100644
--- a/gcc/calls.c
+++ b/gcc/calls.c
@@ -2463,7 +2463,7 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
 	  if (POINTER_TYPE_P (type))
 	    {
 	      access->ptr = args[i].tree_value;
-	      gcc_assert (access->size == NULL_TREE);
+	      // A nonnull ACCESS->SIZE contains VLA bounds.  */
 	    }
 	  else
 	    {
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 70dc1ab73a1..0243d060994 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -5214,6 +5214,7 @@ Options} and @ref{Objective-C and Objective-C++ Dialect Options}.
 
 @gccoptlist{-Waddress   @gol
 -Warray-bounds=1 @r{(only with} @option{-O2}@r{)}  @gol
+-Warray-parameter=2 @r{(C and Objective-C only)} @gol
 -Wbool-compare  @gol
 -Wbool-operation  @gol
 -Wc++11-compat  -Wc++14-compat  @gol
@@ -5265,6 +5266,7 @@ Options} and @ref{Objective-C and Objective-C++ Dialect Options}.
 -Wunused-label     @gol
 -Wunused-value     @gol
 -Wunused-variable  @gol
+-Wvla-parameter @r{(C and Objective-C only)} @gol
 -Wvolatile-register-var  @gol
 -Wzero-length-bounds}
 
@@ -7108,6 +7110,54 @@ pointers. This warning level may give a larger number of
 false positives and is deactivated by default.
 @end table
 
+@item -Warray-parameter
+@itemx -Warray-parameter=@var{n}
+@opindex Wno-array-parameter
+Warn about redeclarations of functions involving arguments of array or
+pointer types of inconsistent kinds or forms, and enable the detection
+of out-of-bounds accesses to such parameters by warnings such as
+@option{-Warray-bounds}.
+
+If the first function declaration uses the array form the bound specified
+in the array is assumed to be the minimum number of elements expected to
+be provided in calls to the function and the maximum number of elements
+accessed by it.  Failing to provide arguments of sufficient size or accessing
+more than the maximum number of elements may be diagnosed by warnings such
+as @option{-Warray-bounds}.  At level 1 the warning diagnoses inconsistencies
+involving array parameters declared using the @code{T[static N]} form.
+
+For example, the warning triggers for the following redeclarations because
+the first one allows an array of any size to be passed to @code{f} while
+the second one with the keyword @code{static} specifies that the array
+argument must have at least four elements.
+
+@smallexample
+void f (int[static 4]);
+void f (int[]);           // warning (inconsistent array form)
+
+void g (void)
+@{
+  int *p = (int *)malloc (4);
+  f (p);                  // warning (array too small)
+  @dots{}
+@}
+@end smallexample
+
+At level 2 the warning also triggers for redeclarations involving any other
+inconsistency in array or pointer argument forms denoting array sizes.
+Pointers and arrays of unspecified bound are considered equivalent and do
+not trigger a warning.
+
+@smallexample
+void g (int*);
+void g (int[]);     // no warning
+void g (int[8]);    // warning (inconsistent array bound)
+@end smallexample
+
+@option{-Warray-parameter=2} is included in @option{-Wall}.  The
+@option{-Wvla-parameter} option triggers warnings for similar inconsistencies
+involving Variable Length Array arguments.
+
 @item -Wattribute-alias=@var{n}
 @itemx -Wno-attribute-alias
 @opindex Wattribute-alias
@@ -8531,6 +8581,44 @@ See also @option{-Walloca-larger-than=@var{byte-size}}.
 Disable @option{-Wvla-larger-than=} warnings.  The option is equivalent
 to @option{-Wvla-larger-than=}@samp{SIZE_MAX} or larger.
 
+@item -Wvla-parameter
+@opindex Wno-vla-parameter
+Warn about redeclarations of functions involving arguments of Variable
+Length Array types of inconsistent kinds or forms, and enable the detection
+of out-of-bounds accesses to such parameters by warnings such as
+@option{-Warray-bounds}.
+
+If the first function declaration uses the VLA form the bound specified
+in the array is assumed to be the minimum number of elements expected to
+be provided in calls to the function and the maximum number of elements
+accessed by it.  Failing to provide arguments of sufficient size or
+accessing more than the maximum number of elements may be diagnosed.
+
+For example, the warning triggers for the following redeclarations because
+the first one allows an array of any size to be passed to @code{f} while
+the second one specifies that the array argument must have at least @code{n}
+elements.  In addition, calling @code{f} with the assotiated VLA bound
+parameter in excess of the actual VLA bound triggers a warning as well.
+
+@smallexample
+void f (int n, int[n]);
+void f (int, int[]);     // warning: argument 2 previously declared as a VLA
+
+void g (int n)
+@{
+    if (n > 4)
+      return;
+    int a[n];
+    f (sizeof a, a);     // warning: access to a by f may be out of bounds
+  @dots{}
+@}
+
+@end smallexample
+
+@option{-Wvla-parameter} is included in @option{-Wall}.  The
+@option{-Warray-parameter} option triggers warnings for similar problems
+involving ordinary array arguments.
+
 @item -Wvolatile-register-var
 @opindex Wvolatile-register-var
 @opindex Wno-volatile-register-var
diff --git a/gcc/testsuite/c-c++-common/Warray-bounds-6.c b/gcc/testsuite/c-c++-common/Warray-bounds-6.c
index 6611d5cd916..5e787360314 100644
--- a/gcc/testsuite/c-c++-common/Warray-bounds-6.c
+++ b/gcc/testsuite/c-c++-common/Warray-bounds-6.c
@@ -1,8 +1,12 @@
-/* PR tree-optimization/86614 */
-/* { dg-do compile } */
-/* { dg-options "-O2 -Warray-bounds" } */
+/* PR tree-optimization/86614 - duplicate -Warray-bounds for a strncpy
+   call with out-of-bounds offset
+   { dg-do compile }
+   { dg-options "-O2 -Warray-bounds" } */
 
-extern char *strncpy (char *, const char *, __SIZE_TYPE__);
+#if __cplusplus
+extern "C"
+#endif
+char *strncpy (char *, const char *, __SIZE_TYPE__);
 
 void sink (void *);
 
@@ -12,7 +16,8 @@ void g (const char *s, unsigned n)
 {
   int i = (char *)a[1].b - (char *)a + 1;
   char *d = a[1].b;
-  /* Ensure the same bug is not diagnosed more than once.  */
-  strncpy (d + i, s, n);	/* { dg-warning "array subscript \[0-9]+ is outside array bounds of" } */
-				/* { dg-bogus "offset \[0-9]+ is out of the bounds \\\[0, \[0-9]+\\\] of object 'a' with type" "" { target *-*-* } .-1 } */
+  /* Verify the bug is diagnosed exactly once, using either form
+     of the warning.  */
+  strncpy (d + i, s, n);	/* { dg-warning "array subscript \[0-9]+ is outside array bounds|offset \[0-9]+ is out of the bounds" } */
+				/* { dg-bogus "offset \[0-9]+ is out of the bounds|array subscript \[0-9]+ is outside array bounds" "" { target *-*-* } .-1 } */
 }
diff --git a/gcc/testsuite/gcc.dg/Warray-parameter-2.c b/gcc/testsuite/gcc.dg/Warray-parameter-2.c
new file mode 100644
index 00000000000..88f20e203ce
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Warray-parameter-2.c
@@ -0,0 +1,45 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+   { dg-do compile }
+   { dg-options "-Wall" } */
+
+// Reduced from Glibc.
+
+typedef struct FILE FILE;
+
+int vfprintf (FILE*, const char*, __builtin_va_list);
+int vfprintf (FILE*, const char*, __builtin_va_list);   // { dg-bogus "-Warray-parameter" }
+int vfprintf (FILE*, const char*, __builtin_va_list);
+
+int vfscanf (FILE*, const char*, __builtin_va_list);
+int vfscanf (FILE*, const char*, __builtin_va_list);    // { dg-bogus "-Warray-parameter" }
+int vfscanf (FILE*, const char*, __builtin_va_list);
+
+
+/* Verify that mismatches in array/to pointer to va_list are still
+   diagnosed.  */
+
+int fva (__builtin_va_list);
+int fva (__builtin_va_list);
+
+int fpva_a1 (__builtin_va_list*);
+int fpva_a1 (__builtin_va_list[1]);     // { dg-warning "\\\[-Warray-parameter" }
+
+int fpva_a_ (__builtin_va_list*);
+int fpva_a_ (__builtin_va_list[]);
+int fpva_a_ (__builtin_va_list*);
+int fpva_a_ (__builtin_va_list[]);
+
+/* Also verify that a mismatch between a pointer and a one-element
+   array are diagnosed.  This is pervasive in Glibc headers but
+   making an exception for it would leave no way to express
+   the requirement that a function take at least one argument
+   by reference.  */
+
+struct __jmp_buf_tag;
+int __sigsetjmp (struct __jmp_buf_tag*, int);
+
+struct __jmp_buf_tag { };
+typedef struct __jmp_buf_tag jmp_buf[1];
+
+int __sigsetjmp (struct __jmp_buf_tag[1], int);   // { dg-warning "\\\[-Warray-parameter" }
diff --git a/gcc/testsuite/gcc.dg/Warray-parameter-3.c b/gcc/testsuite/gcc.dg/Warray-parameter-3.c
new file mode 100644
index 00000000000..cbf3e9339f5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Warray-parameter-3.c
@@ -0,0 +1,89 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+   { dg-do compile }
+   { dg-options "-Wall -Warray-parameter=1" } */
+
+/* Verify that at level 1 mismatches in the bounds of ordinary array
+   parameters don't trigger -Warray-parameter.  */
+void fax (int[]);
+void fax (int[0]);
+void fax (int[1]);
+void fax (int[2]);
+void fax (int[3]);
+
+/* Same as above but starting with an array with a specified bound.  */
+void gax (int[3]);
+void gax (int[2]);
+void gax (int[1]);
+void gax (int[0]);
+void gax (int[]);
+
+/* Same for multidimensional arrays.  */
+void fax_y (int[][3]);
+void fax_y (int[0][3]);
+void fax_y (int[1][3]);
+void fax_y (int[2][3]);
+void fax_y (int[3][3]);
+
+/* Same as above but starting with an array with a specified bound.  */
+void gax_y (int[3][5]);
+void gax_y (int[2][5]);
+void gax_y (int[1][5]);
+void gax_y (int[0][5]);
+void gax_y (int[][5]);
+
+/* Exercise VLAs with a mismatch in the bound for an ordinary array.  */
+void fvlax_y (int n, int[][n]);
+void fvlax_y (int n, int[0][n]);
+void fvlax_y (int n, int[1][n]);
+void fvlax_y (int n, int[2][n]);
+void fvlax_y (int n, int[3][n]);
+
+void fvlaxn_y (int n, int[][n]);
+void fvlaxn_y (int n, int[0][n]);
+void fvlaxn_y (int n, int[1][n]);
+void fvlaxn_y (int n, int[2][n]);
+void fvlaxn_y (int n, int[3][n]);
+
+void fvlaxx_y (int[][*]);
+void fvlaxx_y (int[0][*]);
+void fvlaxx_y (int[1][*]);
+void fvlaxx_y (int[2][*]);
+void fvlaxx_y (int[3][*]);
+
+/* Verify that mismatches in the bounds of array parameters declared
+   static do trigger -Warray-parameter.  */
+void fas1 (int[static 1]);    // { dg-message "previously declared as 'int\\\[static 1]'" }
+void fas1 (int[static 2]);    // { dg-warning "\\\[-Warray-parameter=" }
+
+
+/* Also verify that -Warray-bounds doesn't trigger for ordinary array
+   parameters...  */
+#pragma GCC optimize "2"
+
+__attribute__ ((noipa)) void
+gca3 (char a[3])
+{
+  a[0] = 0; a[1] = 1; a[2] = 2; a[3] = 3;
+}
+
+__attribute__ ((noipa)) void
+gia3 (int a[3])
+{
+  a[0] = 0; a[1] = 1; a[2] = 2; a[3] = 3;
+}
+
+/* ...but does for static arrays.  */
+__attribute__ ((noipa)) void
+gcas3 (char a[static 3])
+{
+  a[0] = 0; a[1] = 1; a[2] = 2;
+  a[3] = 3;                   // { dg-warning "\\\[-Warray-bounds" }
+}
+
+__attribute__ ((noipa)) void
+gias3 (int a[static 3])
+{
+  a[0] = 0; a[1] = 1; a[2] = 2;
+  a[3] = 3;                   // { dg-warning "\\\[-Warray-bounds" }
+}
diff --git a/gcc/testsuite/gcc.dg/Warray-parameter-4.c b/gcc/testsuite/gcc.dg/Warray-parameter-4.c
new file mode 100644
index 00000000000..b702d730a13
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Warray-parameter-4.c
@@ -0,0 +1,119 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+   Verify warnings for multidimensional arrays, including mismatches
+   in bounds of arrays of VLAs.  (Mismatches in variable bounds are
+   diagnosed by -Wvla-parameter.)
+   { dg-do compile }
+   { dg-options "-Wall -Warray-parameter=2" } */
+
+// Verify that equivalent forms don't tigger a warning.
+
+typedef int IA1[1];
+typedef int IA1x_[][1];
+typedef int IA1x0[0][1];
+
+void fia_x1 (int[][1]);
+void fia_x1 (IA1[0]);
+void fia_x1 (IA1x_);
+void fia_x1 (IA1x0);
+void fia_x1 (int[0][1]);
+void fia_x1 (int[static 0][1]);
+
+// Same as above one more time.
+void fia_x1 (int[][1]);
+void fia_x1 (IA1[0]);
+void fia_x1 (IA1x_);
+void fia_x1 (IA1x0);
+void fia_x1 (int[0][1]);
+void fia_x1 (int[static 0][1]);
+
+
+void fia1x1 (int[1][1]);
+void fia1x1 (int[1][1]);
+void fia1x1 (int[static 1][1]);
+
+void fia2x1 (int[2][1]);
+void fia2x1 (int[2][1]);
+void fia2x1 (int[static 2][1]);
+
+void fia1x2 (int[1][2]);
+void fia1x2 (int[1][2]);
+void fia1x2 (int[static 1][2]);
+
+void fia1x1_2x1 (int[1][1]);          // { dg-message "previously declared as 'int\\\[1]\\\[1]'" }
+void fia1x1_2x1 (int[2][1]);          // { dg-warning "\\\[-Warray-parameter" }
+void fia1x1_2x1 (int[static 1][1]);
+void fia1x1_2x1 (int[static 2][1]);   // { dg-warning "\\\[-Warray-parameter" }
+
+
+void fia2x1_1x1 (int[2][1]);          // { dg-message "previously declared as 'int\\\[2]\\\[1]'" }
+void fia2x1_1x1 (int[1][1]);          // { dg-warning "\\\[-Warray-parameter" }
+void fia2x1_1x1 (int[2][1]);
+void fia2x1_1x1 (int[static 1][1]);   // { dg-warning "\\\[-Warray-parameter" }
+void fia2x1_1x1 (int[static 2][1]);
+
+
+extern int n1, n2;
+
+void fca_xn1 (char[][n1]);
+void fca_xn1 (char[0][n1]);
+void fca_xn1 (char[static 0][n1]);
+
+void fca1xn1_2xn1 (char[1][n1]);
+void fca1xn1_2xn1 (char[2][n1]);        // { dg-warning "\\\[-Warray-parameter" }
+void fca1xn1_2xn1 (char[1][n1]);
+void fca1xn1_2xn1 (char[static 1][n1]);
+void fca1xn1_2xn1 (char[static 2][n1]); // { dg-warning "\\\[-Warray-parameter" }
+
+
+/* Exercise VLAs with a mismatch in the bound for an ordinary array.  */
+void fvlax_y (int n, int[][n]);
+void fvlax_y (int n, int[0][n]);
+void fvlax_y (int n, int[1][n]);        // { dg-warning "argument 2 of type 'int\\\[1]\\\[n]' with mismatched bound" }
+void fvlax_y (int n, int[2][n]);        // { dg-warning "argument 2 of type 'int\\\[2]\\\[n]' with mismatched bound" }
+void fvlax_y (int n, int[3][n]);        // { dg-warning "argument 2 of type 'int\\\[3]\\\[n]' with mismatched bound" }
+
+void fvlaxn_y (int n, int[][n]);
+void fvlaxn_y (int n, int[0][n]);
+void fvlaxn_y (int n, int[1][n]);       // { dg-warning "\\\[-Warray-parameter" }
+void fvlaxn_y (int n, int[2][n]);       // { dg-warning "\\\[-Warray-parameter" }
+void fvlaxn_y (int n, int[3][n]);       // { dg-warning "\\\[-Warray-parameter" }
+
+void fvlaxx_y (int[][*]);
+void fvlaxx_y (int[0][*]);
+void fvlaxx_y (int[1][*]);              // { dg-warning "\\\[-Warray-parameter" }
+void fvlaxx_y (int[2][*]);              // { dg-warning "\\\[-Warray-parameter" }
+void fvlaxx_y (int[3][*]);              // { dg-warning "\\\[-Warray-parameter" }
+
+
+// Verify an array of pointers to an array of function pointers.
+
+void ffpa7_5 (void (* (* (* [7])[5])(void))(void));
+// { dg-message "previously declared as 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[7]\\\)\\\[5]\\\)\\\(void\\\)\\\)\\\(void\\\)'" "note" { target *-*-* } .-1 }
+void ffpa7_5 (void (* (* (* [6])[5])(void))(void));
+// { dg-warning "argument 1 of type 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[6]\\\)\\\[5]\\\)\\\(void\\\)\\\)\\\(void\\\)' with mismatched bound" "" { target *-*-* } .-1 }
+void ffpa7_5 (void (* (* (* [])[5])(void))(void));
+// { dg-warning "argument 1 of type 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[]\\\)\\\[5]\\\)\\\(void\\\)\\\)\\\(void\\\)' with mismatched bound" "" { target *-*-* } .-1 }
+void ffpa7_5 (void (* (* (* (*))[5])(void))(void));
+// { dg-warning "argument 1 of type 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\*\\\)\\\[5]\\\)\\\(void\\\)\\\)\\\(void\\\)' declared as a pointer" "" { target *-*-* } .-1 }
+
+// Same as above but with array of pointers to a VLA of function pointers.
+void ffpa7_n1 (void (* (* (* [7])[n1])(void))(void));
+// { dg-message "previously declared as 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[7]\\\)\\\[n1]\\\)\\\(void\\\)\\\)\\\(void\\\)'" "note" { target *-*-* } .-1 }
+void ffpa7_n1 (void (* (* (* [8])[n1])(void))(void));
+// { dg-warning "argument 1 of type 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[8]\\\)\\\[n1]\\\)\\\(void\\\)\\\)\\\(void\\\)' with mismatched bound" "" { target *-*-* } .-1 }
+
+void ffpa9_x (void (* (* (* [9])[*])(void))(void));
+// { dg-message "previously declared as 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[9]\\\)\\\[\\\*]\\\)\\\(void\\\)\\\)\\\(void\\\)'" "pr?????" { xfail *-*-* } .-1 }
+// { dg-message "previously declared as 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[9]\\\)\\\[0]\\\)\\\(void\\\)\\\)\\\(void\\\)'" "" { target *-*-* } .-2 }
+void ffpa9_x (void (* (* (* [8])[*])(void))(void));
+// { dg-warning "argument 1 of type 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[8]\\\)\\\[\\\*]\\\)\\\(void\\\)\\\)\\\(void\\\)' with mismatched bound" "pr?????" { xfail *-*-* } .-1 }
+// { dg-warning "argument 1 of type 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[8]\\\)\\\[0]\\\)\\\(void\\\)\\\)\\\(void\\\)' with mismatched bound"  "" { target *-*-* } .-2 }
+
+/* Verify a three-dimensional array of pointers to two-dimensional arrays
+   of pointers to function pointers.  */
+
+void ffpa7_5_3 (void (* (* (* (* [7])[5])[3])(void))(void));
+// { dg-message "previously declared as 'void ?\\\(\\\* ?\\\(\\\* ?\\\(\\\* ?\\\(\\\* ?\\\[7]\\\)\\\[5]\\\)\\\[3]\\\)\\\(void\\\)\\\)\\\(void\\\)'" "note" { target *-*-* } .-1 }
+void ffpa7_5_3 (void (* (* (* (* [1])[5])[3])(void))(void));
+// { dg-warning "argument 1 of type 'void ?\\\(\\\* ?\\\(\\\* ?\\\(\\\* ?\\\(\\\* ?\\\[1]\\\)\\\[5]\\\)\\\[3]\\\)\\\(void\\\)\\\)\\\(void\\\)' with mismatched bound" "" { target *-*-* } .-1 }
diff --git a/gcc/testsuite/gcc.dg/Warray-parameter-5.c b/gcc/testsuite/gcc.dg/Warray-parameter-5.c
new file mode 100644
index 00000000000..6e89bf0c801
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Warray-parameter-5.c
@@ -0,0 +1,14 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+   Verify that -Warray-parameter diagnoses mismatches in bounds of
+   arrays between redeclarations of the same function and with pointer
+   parameters pointing to those arrays.
+   { dg-do compile }
+   { dg-options "-Wall -Warray-parameter" } */
+
+void fa_x (int (*)[]);        // { dg-message "previously declared as 'int \\\(\\\*\\\)\\\[]'" }
+void fa_x (int (*)[2]);       // { dg-warning "\\\[-Warray-parameter" }
+void fa_x (int (*)[2]);       // { dg-warning "mismatch in bound 1 of argument 1 declared as 'int \\\(\\\*\\\)\\\[2]'" }
+
+void fa_2 (int (*)[2]);       // { dg-message "previously declared as 'int \\\(\\\*\\\)\\\[2]'" }
+void fa_2 (int (*)[]);        // { dg-warning "mismatch in bound 1 of argument 1 declared as 'int \\\(\\\*\\\)\\\[]'" }
diff --git a/gcc/testsuite/gcc.dg/Warray-parameter.c b/gcc/testsuite/gcc.dg/Warray-parameter.c
new file mode 100644
index 00000000000..42be3100e45
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Warray-parameter.c
@@ -0,0 +1,187 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+   Verify that -Warray-parameter diagnoses mismatches in array (and
+   pointer) arrguments between redeclarations of the same function.
+   Also verify that the array/pointer argument form in a mismatched
+   redeclaration doesn't override the form in the initial declaration.
+   { dg-do compile }
+   { dg-options "-Wall -Warray-parameter -Wno-vla-paramater" } */
+
+/* Redclarations with the same or equivalent array form should not
+   be dianosed.  T[0] is diagnosed by -Wpedantic for being invalid
+   C so there's little point in also warning for the difference in
+   array form.  */
+void f1vpp (void**);
+void f1vpp (void*[]);
+void f1vpp (void*[0]);
+
+void f1ia_ (int[]);
+void f1ia_ (int[]);
+void f1ia_ (int[0]);
+/* Verify the unused attribute still has an effect.  */
+void f1ia_ (int a[0] __attribute__ ((unused))) { }
+void f1ia_ (int[]);
+
+void f1ia_p (int[]);
+void f1ia_p (int*);
+void f1ia_p (int *p  __attribute__ ((unused))) { }
+void f1ia_p (int[]);
+
+void f1p_ia (const int*);
+void f1p_ia (const int[]);
+void f1p_ia (const int *p __attribute__ ((unused))) { }
+void f1p_ia (const int[]);
+
+void f1ia1 (int[1]);
+void f1ia1 (int[1]);
+void f1ia1 (int[2 - 1]);
+
+void f1ias2 (int[static 2]);
+void f1ias2 (int[static 2]);
+void f1ias2 (int[static 1 + 1]);
+void f1ias2 (int a[static 3 - 1]) { (void)&a; }
+
+void f1ipa_ (int*[]);
+void f1ipa_ (int*[]);
+void f1ipa_ (int*[0]);
+
+void f1ia1_x (int[1]);          // { dg-message "previously declared as 'int\\\[1]'" }
+void f1ia1_x (int[]);           // { dg-warning "argument 1 of type 'int\\\[]' with mismatched bound" }
+void f1ia1_x (int[]);           // { dg-warning "argument 1 of type 'int\\\[]' with mismatched bound" }
+void f1ia1_x (int[1]);
+void f1ia1_x (int[2]);          // { dg-warning "argument 1 of type 'int\\\[2]' with mismatched bound" }
+void f1ia1_x (int[1]);
+void f1ia1_x (int[3]);          // { dg-warning "argument 1 of type 'int\\\[3]' with mismatched bound" }
+void f1ia1_x (int a[1] __attribute__ ((unused))) { }
+
+
+void f1ias2_s3 (int[static 2]); // { dg-message "previously declared as 'int\\\[static 2]'" }
+void f1ias2_s3 (int[static 3]); // { dg-warning "argument 1 of type 'int\\\[static 3]' with mismatched bound" }
+/* Verify the unused attribute still has an effect and doesn't interfere
+   with the warning.  */
+void f1ias2_s3 (int a[static 3] __attribute__ ((unused))) { }  // { dg-warning "argument 1 of type 'int\\\[static 3]' with mismatched bound" }
+
+
+/* Ordinary T[N] and T[static N] forms are both effectively treated
+   the same but strictly have different meanings so they are diagnosed.
+   It might be worth splitting the warning into two levels and having
+   only the higher level treat the ordinary form as T[static N].  */
+
+void f1ia3_s4 (int[3]);         // { dg-message "previously declared as 'int\\\[3]'" }
+void f1ia3_s4 (int[static 4]);  // { dg-warning "argument 1 of type 'int\\\[static 4]' with mismatched bound" }
+void f1ia3_s4 (int[3]);
+
+
+void f1ias4_5 (int[static 4]);  // { dg-message "previously declared as 'int\\\[static 4]'" }
+void f1ias4_5 (int[5]);         // { dg-warning "argument 1 of type 'int\\\[5]' with mismatched bound" }
+void f1ias4_5 (int[static 4]);
+
+
+void f1ia_1 (int[]);            // { dg-message "previously declared as 'int\\\[]'" }
+void f1ia_1 (int[1]);           // { dg-warning "argument 1 of type 'int\\\[1]' with mismatched bound" }
+void f1ia_1 (int[]);
+
+
+void f1ca_ (char[]);            // { dg-message "previously declared as 'char\\\[]'" }
+void f1ca_ (char[2]);           // { dg-warning "argument 1 of type 'char\\\[2]' with mismatched bound" }
+void f1ca_ (char[]);
+
+
+void f1csp (const short*);      // { dg-message "previously declared as 'const short int ?\\\*'" }
+void f1csp (const short[3]);    // { dg-warning "argument 1 of type 'const short int\\\[3]' with mismatched bound" }
+void f1csp (const short*);
+
+
+void f1ia2 (int[2]);            // { dg-message "previously declared as 'int\\\[2]'" }
+void f1ia2 (int[1]);            // { dg-warning "argument 1 of type 'int\\\[1]' with mismatched bound" }
+void f1ia2 (int[2]);
+
+
+void f1cvla2 (const volatile long[3]);  // { dg-message "previously declared as 'const volatile long int\\\[3]'" }
+void f1cvla2 (const volatile long[2]);  // { dg-warning "argument 1 of type 'const volatile long int\\\[2]' with mismatched bound" }
+void f1cvla2 (const volatile long[3]);
+void f1cvla2 (const volatile long[restrict 4]); // { dg-warning "argument 1 of type 'const volatile long int\\\[restrict 4]' with mismatched bound" }
+
+
+void f1afa4 (_Atomic float[3]);         // { dg-message "previously declared as an array '_Atomic float ?\\\[3]'" }
+void f1afa4 (_Atomic float*);           // { dg-warning "argument 1 of type '_Atomic float ?\\\*' declared as a pointer" }
+void f1afa4 (_Atomic float[3]);
+
+void f1ipa1_a2 (int*[1]);       // { dg-message "previously declared as 'int \\\*\\\[1]'" }
+void f1ipa1_a2 (int*[2]);       // { dg-warning "argument 1 of type 'int \\\*\\\[2]' with mismatched bound" }
+void f1ipa1_a2 (int*[1]);
+
+
+typedef int IAx[];
+typedef int IA1[1];
+typedef int IA2[2];
+typedef int IA3[3];
+
+// The message should differentiate between the [] form and *.
+void f1IAx_A1 (IAx);            // { dg-message "previously declared as 'int\\\[]'" "pr?????" { xfail *-*-* } }
+                                // { dg-message "previously declared as 'int *\\\*'" "note" { target *-*-* } .-1 }
+void f1IAx_A1 (IA1);            // { dg-message "argument 1 of type 'int\\\[1]' with mismatched bound" }
+
+void f1IA1_A2 (IA1);            // { dg-message "previously declared as 'int\\\[1]'" }
+void f1IA1_A2 (IA2);            // { dg-warning "argument 1 of type 'int\\\[2]' with mismatched bound" }
+void f1IA1_A2 (IA1);
+void f1IA1_A2 (int[2]);         // { dg-warning "argument 1 of type 'int\\\[2]' with mismatched bound" }
+
+
+void f1IA1_A3 (IA1 ia1);        // { dg-message "previously declared as 'int\\\[1]'" }
+void f1IA1_A3 (IA3 ia3);        // { dg-warning "argument 1 of type 'int\\\[3]' with mismatched bound" }
+void f1IA1_A3 (IA1 ia1);
+
+
+void f1IA2_A3 (IA2 a);          // { dg-message "previously declared as 'int\\\[2]'" }
+void f1IA2_A3 (IA3 a);          // { dg-warning "argument 1 of type 'int\\\[3]' with mismatched bound" }
+void f1IA2_A3 (IA2 a);
+
+
+// Verify multiple array arguments.
+
+void f2a2_a3_3_3 (int[2], int[3]);  // { dg-message "previously declared as 'int\\\[2]'" }
+void f2a2_a3_3_3 (int[2], int[3]);
+void f2a2_a3_3_3 (int[3], int[3]);  // { dg-warning "argument 1 of type 'int\\\[3]' with mismatched bound" }
+
+
+void f2a2_a3_2_4 (int[2], int[3]);  // { dg-message "previously declared as 'int\\\[3]'" }
+void f2a2_a3_2_4 (int[2], int[4]);  // { dg-warning "argument 2 of type 'int\\\[4]' with mismatched bound" }
+
+
+/* Verify that pointers to arrays and arrays of arrays are differentiated
+   the same way as pointers and arrays of other types.  */
+typedef IA1 *PA1;
+
+void fpia1 (IA1*);              // { dg-message "previously declared as 'int ?\\(\\\*\\)\\\[1]'" }
+void fpia1 (IA1[1]);            // { dg-warning "argument 1 of type 'int\\\[1]\\\[1]' with mismatched bound" }
+void fpia1 (PA1);
+void fpia1 (int(*)[1]);
+void fpia1 (int[][1]);
+
+void f1vpa1 (void*[][1]);
+void f1vpa1 (void*[0][1]);
+
+/* Verify arrays of pointers.  */
+void vaip1 (int (*[3]));       // { dg-message "previously declared as 'int *\\\*\\\[3]'" }
+void vaip1 (int (*[5]));       // { dg-warning "argument 1 of type 'int *\\\*\\\[5]' with mismatched bound" }
+void vaip1 (int (*[3]));
+void vaip1 (int (*[]));        // { dg-warning "argument 1 of type 'int *\\\*\\\[]' with mismatched bound" }
+void vaip1 (int (*[3]));
+
+/* Verify that attributes with arrays don't cause unwanted warnings and
+   don't suppress intended ones.  */
+
+#define ALIGN(N)__attribute__ ((aligned (__alignof__ (char[N]))))
+
+void fatipa2 (int (* ALIGN (3)[2]));  // { dg-message "previously declared as 'int \\\*\\\[2]'" }
+void fatipa2 (int (* ALIGN (4)[2]));
+void fatipa2 (int (* ALIGN (5)[2]));
+void fatipa2 (int (* ALIGN (7)[3]));  // { dg-warning "argument 1 of type 'int \\\*\\\[3]' with mismatched bound" }
+
+void fatiap (int (* ALIGN (3))[2]);
+void fatiap (int (* ALIGN (5))[2]);
+
+
+void fatipa3 (int (* ALIGN (1) (* ALIGN (2))[3]));
+void fatipa3 (int (* ALIGN (1) (* ALIGN (2))[3]));
diff --git a/gcc/testsuite/gcc.dg/Wbuiltin-declaration-mismatch-9.c b/gcc/testsuite/gcc.dg/Wbuiltin-declaration-mismatch-9.c
index f0c1ce33267..da767b87700 100644
--- a/gcc/testsuite/gcc.dg/Wbuiltin-declaration-mismatch-9.c
+++ b/gcc/testsuite/gcc.dg/Wbuiltin-declaration-mismatch-9.c
@@ -10,3 +10,6 @@ void a (void)
 	  ""        /* { dg-warning "passing argument 2 of .sscanf. from incompatible pointer type" } */
 	  );
 }
+
+/* The scanf call may also trigger:
+   { dg-prune-output "-Wstringop-overflow" } */
diff --git a/gcc/testsuite/gcc.dg/Wvla-parameter-2.c b/gcc/testsuite/gcc.dg/Wvla-parameter-2.c
new file mode 100644
index 00000000000..b86a78d3a8c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wvla-parameter-2.c
@@ -0,0 +1,60 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+   Verify the -Wvla-parameter warnings correctly diagnose mismatches
+   between VLA arguments in redeclarations of the same function.
+   { dg-do compile }
+   { dg-options "-Wall -Wvla-parameter" } */
+
+void fmn_a1n_axn (int n, int[1][n]);            // { dg-message "previously declared as 'int\\\[1]\\\[n]' with 1 variable bound" "note" }
+void fmn_a1n_axn (int n, int[*][n]);            // { dg-warning "argument 2 of type 'int\\\[\\\*]\\\[n]' declared with 2 variable bounds" }
+
+
+void fmn_axn_a2n (int n, int[*][n]);            // { dg-message "previously declared as 'int\\\[\\\*]\\\[n]' with 2 variable bounds" "note" }
+void fmn_axn_a2n (int n, int[2][n]);            // { dg-warning "argument 2 of type 'int\\\[2]\\\[n]' declared with 1 variable bound" }
+
+
+void fmn_amn_axn (int m, int n, int[m][n]);     // { dg-message "previously declared as 'int\\\[m]\\\[n]' with 0 unspecified variable bounds" "note" }
+void fmn_amn_axn (int m, int n, int[*][n]);     // { dg-warning "argument 3 of type 'int\\\[\\\*]\\\[n]' declared with 1 unspecified variable bound" }
+
+// Same as above but a different function name.
+void gmn_amn_axn (int m, int n, int[m][n]);     // { dg-message "previously declared as 'int\\\[m]\\\[n]' with 0 unspecified variable bounds" "note" }
+void gmn_amn_axn (int m, int n, int[*][n]);     // { dg-warning "argument 3 of type 'int\\\[\\\*]\\\[n]' declared with 1 unspecified variable bound" }
+
+typedef int A7[7];
+
+void fm_A7_m_5 (int m, A7[m][5]);               // { dg-message "previously declared as 'int\\\[m]\\\[5]\\\[7]' with bound argument 1" "note" }
+void fm_A7_m_5 (int n, A7[n][5]);
+
+void fm_A7_m_5 (int n, A7[n + 1][5]);           // { dg-warning "argument 2 of type 'int\\\[n \\\+ 1]\\\[5]\\\[7]' declared with mismatched bound 'n \\\+ 1'" }
+
+
+int n1, n2, n3, n4, n5, n6, n7, n8, n9;
+void f (int[n1][2][n3][4][n5][6][n7][8][n9]);   // { dg-message "previously declared as 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[n5]\\\[6]\\\[n7]\\\[8]\\\[n9]' with 0 unspecified variable bounds" "note" }
+                                                // { dg-message "with 5 variable bounds" "note" { target *-*-* } .-1  }
+void f (int[n1][2][n3][4][n5][6][n7][8][n9]);
+
+/* Due to a limitation and because [*] is represented the same as [0]
+   only the most significant array bound is rendered as [*]; the others
+   are rendered as [0].  */
+void f (int[n1][2][n3][4][n5][6][n7][8][*]);    // { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[n5]\\\[6]\\\[n7]\\\[8]\\\[\\\*]' declared with 1 unspecified variable bound" "pr?????" { xfail *-*-* } }
+// { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[n5]\\\[6]\\\[n7]\\\[8]\\\[0]' declared with 1 unspecified variable bound" "pr?????" { target *-*-* } .-1 }
+void f (int[n1][2][n3][4][n5][6][*][8][n9]);   // { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[n5]\\\[6]\\\[\\\*]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" "pr?????" { xfail *-*-* } }
+// { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[n5]\\\[6]\\\[0]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" "pr?????" { target *-*-* } .-1 }
+void f (int[n1][2][n3][4][*][6][n7][8][n9]);   // { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[\\\*]\\\[6]\\\[n7]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" "pr?????" { xfail *-*-*} }
+// { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[0]\\\[6]\\\[n7]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" "pr?????" { target *-*-* } .-1 }
+void f (int[n1][2][*][4][n5][6][n7][8][n9]);   // { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[\\\*]\\\[4]\\\[n5]\\\[6]\\\[n7]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" "pr?????" { xfail *-*-* } }
+// { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[0]\\\[4]\\\[n5]\\\[6]\\\[n7]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" "pr?????" { target *-*-* } .-1 }
+void f (int[*][2][n3][4][n5][6][n7][8][n9]);   // { dg-warning "argument 1 of type 'int\\\[\\\*]\\\[2]\\\[n3]\\\[4]\\\[n5]\\\[6]\\\[n7]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" }
+
+void f (int[n1][n2][n3][n4][n5][n6][n7][n8][n9]);   // { dg-warning "argument 1 of type 'int\\\[n1]\\\[n2]\\\[n3]\\\[n4]\\\[n5]\\\[n6]\\\[n7]\\\[n8]\\\[n9]' declared with 9 variable bounds" }
+
+// Verify that arrays of pointers to arrays...etc are handled correctly.
+void a2pampan (int (*(*(*[2])[n1])[n2]));
+// { dg-message "previously declared as 'int \\\* \\\(\\\* \\\(\\\*\\\[2]\\\)\\\[n1]\\\)\\\[n2]'" "note" { target *-*-* } .-1 }
+void a2pampan (int (*(*(*[2])[n1])[1]));
+// { dg-warning "argument 1 of type 'int \\\* \\\(\\\* \\\(\\\*\\\[2]\\\)\\\[n1]\\\)\\\[1]' declared with 1 variable bound" "" { target *-*-* } .-1  }
+void a2pampan (int (*(*(*[2])[1])[n2]));
+// { dg-warning "argument 1 of type 'int \\\* \\\(\\\* \\\(\\\*\\\[2]\\\)\\\[1]\\\)\\\[n2]' declared with 1 variable bound" "" { target *-*-* } .-1  }
+void a2pampan (int (*(*(*[2])[n1])[n1]));
+// { dg-warning "argument 1 of type 'int \\\* \\\(\\\* \\\(\\\*\\\[2]\\\)\\\[n1]\\\)\\\[n1]' declared with mismatched bound 'n1'" "" { target *-*-* } .-1  }
+void a2pampan (int (*(*(*[2])[n1])[n2]));
diff --git a/gcc/testsuite/gcc.dg/Wvla-parameter-3.c b/gcc/testsuite/gcc.dg/Wvla-parameter-3.c
new file mode 100644
index 00000000000..51f01729b1d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wvla-parameter-3.c
@@ -0,0 +1,68 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+   Verify that redeclarations of functions with pointer parameters to
+   arrays with variable bounds are diagnosed if the bounds don't match
+   either in kind or in the variable expression.
+   { dg-do compile }
+   { dg-options "-Wall -Wvla-parameter" } */
+
+extern int m, n;
+
+void pa_ (int (*)[]);                   // { dg-message "previously declared as 'int \\\(\\\*\\\)\\\[]'" "note" }
+void pa_ (int (*)[n]);                  // { dg-warning "\\\[-Wvla-parameter" }
+void pa_ (int (*)[n + 1]);              // { dg-warning "mismatch in bound 1 of argument 1 declared as 'int *\\\(\\\*\\\)\\\[n \\\+ 1\\\]'" }
+
+void ppa_ (int (**)[]);                 // { dg-message "previously declared as 'int \\\(\\\*\\\*\\\)\\\[]'" "note" }
+void ppa_ (int (**)[n]);                // { dg-warning "\\\[-Wvla-parameter" }
+void ppa_ (int (**)[n + 1]);            // { dg-warning "mismatch in bound 1 of argument 1 declared as 'int \\\(\\\*\\\*\\\)\\\[n \\\+ 1\\\]'" }
+
+void pa1 (int (*)[1]);                  // { dg-message "previously declared as 'int \\\(\\\*\\\)\\\[1]'" "note" }
+void pa1 (int (*)[n]);                  // { dg-warning "\\\[-Wvla-parameter" }
+void pa1 (int (*)[1]);
+void pa1 (int (*)[n + 1]);              // { dg-warning "mismatch in bound 1 of argument 1 declared as 'int *\\\(\\\*\\\)\\\[n \\\+ 1\\\]'" }
+
+void ppax (int (**)[*]);                // { dg-message "previously declared as 'int \\\(\\\*\\\*\\\)\\\[.]'" "note" }
+void ppax (int (**)[n]);                // { dg-warning "\\\[-Wvla-parameter" }
+/* A VLA with an unspecified bound is represented the same as [0] so
+   so the pretty printer can't differentiate between the two forms.  */
+void ppax (int (**)[1]);                // { dg-bogus "\\\[-Warray-parameter" "pr?????" { xfail *-*-* } }
+                                        // { dg-warning "\\\[-Wvla-parameter" "pr?????" { xfail *-*-* } .-1 }
+void ppax (int (**)[n + 1]);            // { dg-warning "mismatch in bound 1 of argument 1 declared as 'int *\\\(\\\*\\\*\\\)\\\[n \\\+ 1\\\]'" }
+
+
+void pa1_n (int (*)[1][n]);
+void pa1_n (int (*)[1][n]);
+void pa1_n (int (*)[*][n]);             // { dg-warning "mismatch in bound 1 of argument 1 declared as 'int \\\(\\\*\\\)\\\[\\\*]\\\[n]'" "pr?????" { xfail *-*-*} }
+                                        // { dg-warning "mismatch in bound 1 of argument 1 declared as 'int \\\(\\\*\\\)\\\[0]\\\[n]'" "pr?????" { target *-*-* } .-1 }
+
+void pa1_n_2 (int (*)[1][n][2]);
+void pa1_n_2 (int (*)[1][n][*]);        // { dg-warning "mismatch in bound 3 of argument 1 declared as 'int \\\(\\\*\\\)\\\[1]\\\[n]\\\[\\\*]'" "pr?????" { xfail *-*-* } }
+                                        // { dg-warning "mismatch in bound 3 of argument 1 declared as 'int \\\(\\\*\\\)\\\[1]\\\[n]\\\[0]'" "pr?????" { target *-*-* } .-1 }
+
+
+void pa1_n_2_a1_n_2 (int (*)[1][n][2], int (*)[1][n][2]);
+// { dg-message "previously declared as 'int \\\(\\\*\\\)\\\[1]\\\[n]\\\[2]'" "note" { target *-*-* } .-1 }
+void pa1_n_2_a1_n_2 (int (*)[1][n][2], int (*)[1][n][n]);
+// { dg-warning "mismatch in bound 3 of argument 2 declared as 'int \\\(\\\*\\\)\\\[1]\\\[n]\\\[n]'" "" { target *-*-* } .-1 }
+void pa1_n_2_a1_n_2 (int (*)[1][n][2], int (*)[1][3][2]);
+// { dg-warning "mismatch in bound 2 of argument 2 declared as 'int \\\(\\\*\\\)\\\[1]\\\[3]\\\[2]'" "" { target *-*-* } .-1 }
+void pa1_n_2_a1_n_2 (int (*)[1][n][2], int (*)[n][n][2]);
+// { dg-warning "mismatch in bound 1 of argument 2 declared as 'int \\\(\\\*\\\)\\\[n]\\\[n]\\\[2]'" "" { target *-*-* } .-1 }
+void pa1_n_2_a1_n_2 (int (*)[1][n][n], int (*)[1][n][2]);
+// { dg-warning "mismatch in bound 3 of argument 1 declared as 'int \\\(\\\*\\\)\\\[1]\\\[n]\\\[n]'" "" { target *-*-* } .-1 }
+void pa1_n_2_a1_n_2 (int (*)[n][n][2], int (*)[1][n][2]);
+// { dg-warning "mismatch in bound 1 of argument 1 declared as 'int \\\(\\\*\\\)\\\[n]\\\[n]\\\[2]'" "" { target *-*-* } .-1 }
+void pa1_n_2_a1_n_2 (int (*)[*][*][*], int (*)[*][*][2]);
+// { dg-warning "mismatch in bounds 1, 2, 3 of argument 1 declared as 'int \\\(\\\*\\\)\\\[.]\\\[.]\\\[.]'" "" { target *-*-* } .-1 }
+// { dg-warning "mismatch in bounds 1, 2 of argument 2 declared as 'int \\\(\\\*\\\)\\\[.]\\\[.]\\\[2]'" "" { target *-*-* } .-2 }
+void pa1_n_2_a1_n_2 (int (*)[1][n][2], int (*)[1][n][2]);
+
+/* Verify that pointers to arrays of pointers to arrays...etc are handled
+   correctly.  */
+void pa2pampan (int (*(*(*(*)[2])[m])[n]));
+// { dg-message "previously declared as 'int \\\* \\\(\\\* \\\(\\\* \\\(\\\*\\\)\\\[2]\\\)\\\[m]\\\)\\\[n]'" "note" { target *-*-* } .-1 }
+void pa2pampan (int (*(*(*(*)[2])[m])[1]));
+// { dg-warning "mismatch in bound 3 of argument 1 declared as 'int \\\* \\\(\\\* \\\(\\\* \\\(\\\*\\\)\\\[2]\\\)\\\[m]\\\)\\\[1]'" "" { target *-*-* } .-1  }
+void pa2pampan (int (*(*(*(*)[2])[1])[n]));
+// { dg-warning "mismatch in bound 2 of argument 1 declared as 'int \\\* \\\(\\\* \\\(\\\* \\\(\\\*\\\)\\\[2]\\\)\\\[1]\\\)\\\[n]'" "" { target *-*-* } .-1  }
+void pa2pampan (int (*(*(*(*)[2])[m])[n]));
diff --git a/gcc/testsuite/gcc.dg/Wvla-parameter.c b/gcc/testsuite/gcc.dg/Wvla-parameter.c
new file mode 100644
index 00000000000..c3d7b7fc0d6
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wvla-parameter.c
@@ -0,0 +1,149 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+   Verify the -Wvla-parameter warnings correctly diagnose mismatches
+   between VLA and non-VLA arguments in redeclarations of the same
+   function.
+   Also verify that the array/pointer argument form in a mismatched
+   redeclaration doesn't override the form in the initial declaration.
+   { dg-do compile }
+   { dg-options "-Wall -Wvla-parameter" } */
+
+/* Verify that redeclaring an argument as a VLA with an unspecified
+   bound that was first declared as an ordinary array with an unspecified
+   bound triggers a warning.  */
+void f1ia_x (int[]);          // { dg-message "previously declared as an ordinary array 'int\\\[]'" "note" }
+void f1ia_x (int[*]);         // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ia_x (int[]);
+void f1ia_x (int[*]);         // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+/* Also verify that a definition of the same form as the first declaration
+   doesn't trigger a warning and doesn't prevent warnings for subsequent
+   mismatches.  */
+void f1ia_x (int a[]) { (void)&a;}
+void f1ia_x (int[*]);         // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+
+/* Repeat the above but starting with an ordinary array with a constant
+   bound.  */
+void f1ia1x (int[1]);          // { dg-message "previously declared as an ordinary array 'int\\\[1]'" "note" }
+void f1ia1x (int[*]);         // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ia1x (int a[1]) { (void)&a; }
+void f1ia1x (int[1]);
+void f1ia1x (int[*]);         // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+
+void f1ipx (int*);            // { dg-message "previously declared as a pointer 'int ?\\\*'" "note" }
+void f1ipx (int[*]);          // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ipx (int*);
+void f1ipx (int *p) { (void)&p; }
+void f1ipx (int[*]);          // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ipx (int*);
+
+void f2ipx (int*, int*);      // { dg-message "previously declared as a pointer 'int ?\\\*'" "note" }
+void f2ipx (int*, int[*]);    // { dg-warning "argument 2 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f2ipx (int*, int*);
+void f2ipx (int*, int[*]);    // { dg-warning "argument 2 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f2ipx (int *p, int *q) { (void)&p; (void)&q; }
+void f2ipx (int*, int[*]);    // { dg-warning "argument 2 of type 'int\\\[\\\*]' declared as a variable length array" }
+
+void f1ias2x (int[static 2]); // { dg-message "previously declared as an ordinary array 'int\\\[static 2]'" }
+void f1ias2x (int[*]);        // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ias2x (int[static 2]);
+void f1ias2x (int[*]);        // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ias2x (int a[static 2]) { (void)&a; }
+void f1ias2x (int[*]);        // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ias2x (int[static 2]);
+
+extern int nelts;
+
+void f1sa_var (short[]);      // { dg-message "previously declared as an ordinary array 'short int\\\[]'" }
+void f1sa_var (short[nelts]); // { dg-warning "argument 1 of type 'short int\\\[nelts]' declared as a variable length array" }
+void f1sa_var (short[]);
+void f1sa_var (short[nelts]); // { dg-warning "argument 1 of type 'short int\\\[nelts]' declared as a variable length array" }
+void f1sa_var (short a[]) { (void)&a; }
+void f1sa_var (short[nelts]); // { dg-warning "argument 1 of type 'short int\\\[nelts]' declared as a variable length array" }
+void f1sa_var (short[]);
+
+void f1sa_expr (int[]);           // { dg-message "previously declared as an ordinary array 'int\\\[]'" }
+void f1sa_expr (int[nelts + 1]);  // { dg-warning "argument 1 of type 'int\\\[nelts \\\+ 1]' declared as a variable length array" }
+void f1sa_expr (int[]);
+void f1sa_expr (int[nelts * 2]);  // { dg-warning "argument 1 of type 'int\\\[nelts \\\* 2]' declared as a variable length array" }
+void f1sa_expr (int a[]) { (void)&a; }
+void f1sa_expr (int[nelts / 3]);  // { dg-warning "argument 1 of type 'int\\\[nelts / 3]' declared as a variable length array" }
+void f1sa_expr (int[]);
+
+extern int f (int);
+
+void f1ia_f (int[]);          // { dg-message "previously declared as an ordinary array 'int\\\[]'" }
+void f1ia_f (int[f (1)]);     // { dg-warning "argument 1 of type 'int\\\[f *\\\(1\\\)]' declared as a variable length array" }
+void f1ia_f (int[]);
+void f1ia_f (int[f (2)]);     // { dg-warning "argument 1 of type 'int\\\[f *\\\(2\\\)]' declared as a variable length array" }
+void f1ia_f (int a[]) { (void)&a; }
+void f1ia_f (int[f (3)]);     // { dg-warning "argument 1 of type 'int\\\[f *\\\(3\\\)]' declared as a variable length array" }
+void f1ia_f (int[f (4)]);     // { dg-warning "argument 1 of type 'int\\\[f *\\\(4\\\)]' declared as a variable length array" }
+void f1ia_f (int[]);
+
+void f1iaf0_f1 (int[f (0)]);  // { dg-message "previously declared as 'int\\\[f *\\\(0\\\)]'" }
+void f1iaf0_f1 (int[f (1)]);  // { dg-warning "argument 1 of type 'int\\\[f *\\\(1\\\)]' declared with mismatched bound" }
+void f1iaf0_f1 (int[f (0)]);
+void f1iaf0_f1 (int[f (1)]);  // { dg-warning "argument 1 of type 'int\\\[f *\\\(1\\\)]' declared with mismatched bound" }
+void f1iaf0_f1 (int a[f (0)]) { (void)&a; }
+void f1iaf0_f1 (int[f (1)]);  // { dg-warning "argument 1 of type 'int\\\[f *\\\(1\\\)]' declared with mismatched bound" }
+void f1iaf0_f1 (int[f (0)]);
+
+void f1la_ (long[]);         // { dg-message "previously declared as an ordinary array 'long int\\\[]'" }
+void f1la_ (long[nelts]);    // { dg-warning "argument 1 of type 'long int\\\[nelts]' declared as a variable length array" }
+void f1la_ (long[]);
+void f1la_ (long a[nelts])   // { dg-warning "argument 1 of type 'long int\\\[nelts]' declared as a variable length array" }
+{ (void)&a; }
+void f1la_ (long[]);
+
+void f2ca_ (int, char[]);     // { dg-message "previously declared as an ordinary array 'char\\\[]'" }
+void f2ca_ (int n, char[n]);  // { dg-warning "argument 2 of type 'char\\\[n]' declared as a variable length array" }
+void f2ca_ (int, char[]);
+void f2ca_ (int n, char a[n]) // { dg-warning "argument 2 of type 'char\\\[n]' declared as a variable length array" }
+{ (void)&n; (void)&a; }
+
+void f2ia1_f (int n, int[n]);     // { dg-message "previously declared as 'int\\\[n]' with bound argument 1" }
+void f2ia1_f (int,   int[f (0)]); // { dg-warning "argument 2 of type 'int\\\[f *\\\(0\\\)]' declared with mismatched bound 'f *\\\(0\\\)'" }
+void f2ia1_f (int m, int[m]);
+void f2ia1_f (int,   int[f (1)]); // { dg-warning "argument 2 of type 'int\\\[f *\\\(1\\\)]' declared with mismatched bound 'f *\\\(1\\\)'" }
+void f2ia1_f (int x, int a[x]) { (void)&x; (void)&a; }
+void f2ia1_f (int,   int[f (2)]);   // { dg-warning "argument 2 of type 'int\\\[f *\\\(2\\\)]' declared with mismatched bound 'f *\\\(2\\\)'" }
+void f2ia1_f (int y, int[y]);
+
+void f2iaf_1 (int,   int[f (0)]); // { dg-message "previously declared as 'int\\\[f *\\\(0\\\)]'" }
+void f2iaf_1 (int n, int[n]);     // { dg-warning "argument 2 of type 'int\\\[n]' declared with mismatched bound argument 1" }
+void f2iaf_1 (int,   int[f (0)]);
+void f2iaf_1 (int m, int[m]);     // { dg-warning "argument 2 of type 'int\\\[m]' declared with mismatched bound argument 1" }
+void f2iaf_1 (int x, int a[f (0)]) { (void)&x; (void)&a; }
+void f2iaf_1 (int y, int[y]);     // { dg-warning "argument 2 of type 'int\\\[y]' declared with mismatched bound argument 1" }
+
+
+void f3ia1 (int n, int, int[n]);  // { dg-message "previously declared as 'int\\\[n]' with bound argument 1" }
+void f3ia1 (int, int n, int[n]);  // { dg-warning "argument 3 of type 'int\\\[n]' declared with mismatched bound argument 2" }
+void f3ia1 (int n, int, int[n]);
+
+
+extern int g (int);
+
+void f1iaf_g (int[f (1)]);    // { dg-message "previously declared as 'int\\\[f *\\\(1\\\)]'" }
+void f1iaf_g (int[g (1)]);    // { dg-warning "argument 1 of type 'int\\\[g *\\\(1\\\)]' declared with mismatched bound" }
+void f1iaf_g (int[f (1)]);
+
+
+void nrf1iaf_g (int[f (1)]);  // { dg-message "previously declared as 'int\\\[f *\\\(1\\\)]'" }
+__attribute__ ((nonnull))
+void nrf1iaf_g (int[g (1)]);  // { dg-warning "argument 1 of type 'int\\\[g *\\\(1\\\)]' declared with mismatched bound" }
+__attribute__ ((noreturn))
+void nrf1iaf_g (int[f (1)]);
+
+/* Verify that the presence or absence of static with VLA dooesn't cause
+   unwanted warnings.  */
+
+int f2ia1_1 (int n, int [n][n]);            // { sg-message "previously declared as 'int\\\[n]\\\[n]' with bound argument 1" }
+int f2ia1_1 (int n, int[static n][n]);
+int f2ia1_1 (int n, int a[static n][n]) { return sizeof *a; }
+int f2ia1_1 (int n, int[static n + 1][n]);  // { dg-warning "argument 2 of type 'int\\\[n \\\+ 1]\\\[n]' declared with mismatched bound 'n \\\+ 1'" }
+
+int f2ias1_1 (int n, int [static n][n]);    // { dg-message "previously declared as 'int\\\[n]\\\[n]' with bound argument 1" }
+int f2ias1_1 (int n, int[n][n]);
+int f2ias1_1 (int n, int a[++n][n])         // { dg-warning "argument 2 of type 'int\\\[\\\+\\\+n]\\\[n]' declared with mismatched bound ' ?\\+\\+n'" }
+{ return sizeof *a; }
diff --git a/gcc/tree-pretty-print.c b/gcc/tree-pretty-print.c
index 655061c174d..ad3dc59bcfe 100644
--- a/gcc/tree-pretty-print.c
+++ b/gcc/tree-pretty-print.c
@@ -1682,9 +1682,9 @@ dump_generic_node (pretty_printer *pp, tree node, int spc, dump_flags_t flags,
 	  pp_string (pp, "atomic ");
 	if (quals & TYPE_QUAL_CONST)
 	  pp_string (pp, "const ");
-	else if (quals & TYPE_QUAL_VOLATILE)
+	if (quals & TYPE_QUAL_VOLATILE)
 	  pp_string (pp, "volatile ");
-	else if (quals & TYPE_QUAL_RESTRICT)
+	if (quals & TYPE_QUAL_RESTRICT)
 	  pp_string (pp, "restrict ");
 
 	if (!ADDR_SPACE_GENERIC_P (TYPE_ADDR_SPACE (node)))

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH 2/5] C front end support to detect out-of-bounds accesses to array parameters
  2020-08-19 22:56             ` Martin Sebor
@ 2020-08-20  0:09               ` Joseph Myers
  2020-08-21 19:17                 ` Martin Sebor
  2020-08-25 18:44                 ` Martin Sebor
  0 siblings, 2 replies; 33+ messages in thread
From: Joseph Myers @ 2020-08-20  0:09 UTC (permalink / raw)
  To: Martin Sebor; +Cc: gcc-patches

On Wed, 19 Aug 2020, Martin Sebor via Gcc-patches wrote:

> > I think you need a while loop there, not just an if, to account for the
> > case of multiple consecutive cdk_attrs.  At least the GNU attribute syntax
> > 
> >     direct-declarator:
> > [...]
> >       ( gnu-attributes[opt] declarator )
> > 
> > should produce multiple consecutive cdk_attrs for each level of
> > parentheses with attributes inside.
> 
> I had considered a loop but couldn't find a way to trigger what you
> describe (or a test in the testsuite that would do it) so I didn't
> use one.  I saw loops like that in other places but I couldn't get
> even those to uncover such a test case.  Here's what I tried:
> 
>   #define A(N) __attribute__ ((aligned (N), may_alias))
>   int n;
>   void f (int (* A (2) A (4) (* A (2) A (4) (* A (2) A (4) [n])[n])));
> 
> Sequences of consecutive attributes are all chained together.
> 
> I've added the loop here but I have no test for it.  It would be
> good to add one if it really is needed.

The sort of thing I'm thinking of would be, where A is some attribute:

void f (int (A (A (A arg))));

(that example doesn't involve an array, but it illustrates the syntax I'd 
expect to produce multiple consecutive cdk_attrs).

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH 2/5] C front end support to detect out-of-bounds accesses to array parameters
  2020-08-20  0:09               ` Joseph Myers
@ 2020-08-21 19:17                 ` Martin Sebor
  2020-08-25 18:44                 ` Martin Sebor
  1 sibling, 0 replies; 33+ messages in thread
From: Martin Sebor @ 2020-08-21 19:17 UTC (permalink / raw)
  To: Joseph Myers; +Cc: gcc-patches

On 8/19/20 6:09 PM, Joseph Myers wrote:
> On Wed, 19 Aug 2020, Martin Sebor via Gcc-patches wrote:
> 
>>> I think you need a while loop there, not just an if, to account for the
>>> case of multiple consecutive cdk_attrs.  At least the GNU attribute syntax
>>>
>>>      direct-declarator:
>>> [...]
>>>        ( gnu-attributes[opt] declarator )
>>>
>>> should produce multiple consecutive cdk_attrs for each level of
>>> parentheses with attributes inside.
>>
>> I had considered a loop but couldn't find a way to trigger what you
>> describe (or a test in the testsuite that would do it) so I didn't
>> use one.  I saw loops like that in other places but I couldn't get
>> even those to uncover such a test case.  Here's what I tried:
>>
>>    #define A(N) __attribute__ ((aligned (N), may_alias))
>>    int n;
>>    void f (int (* A (2) A (4) (* A (2) A (4) (* A (2) A (4) [n])[n])));
>>
>> Sequences of consecutive attributes are all chained together.
>>
>> I've added the loop here but I have no test for it.  It would be
>> good to add one if it really is needed.
> 
> The sort of thing I'm thinking of would be, where A is some attribute:
> 
> void f (int (A (A (A arg))));
> 
> (that example doesn't involve an array, but it illustrates the syntax I'd
> expect to produce multiple consecutive cdk_attrs).

Yes, that does it, thanks.  But as a result of the test:

       if (pd->kind != cdk_array)
	continue;

I don't see how to write a declaration where the if rather than
a loop would cause trouble.  If next->kind == cdk_attrs after
the test in the if statement (i.e., before I replaced it with
the while loop), the test above would be true and the for loop
would continue.  The next test for next->kind would then skip
over the attrs.

Let me know if I'm missing something.  Otherwise I'll just leave
the loop there with no test.

Martin

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH 2/5] C front end support to detect out-of-bounds accesses to array parameters
  2020-08-20  0:09               ` Joseph Myers
  2020-08-21 19:17                 ` Martin Sebor
@ 2020-08-25 18:44                 ` Martin Sebor
  2020-09-03  0:03                   ` [PING][PATCH " Martin Sebor
  1 sibling, 1 reply; 33+ messages in thread
From: Martin Sebor @ 2020-08-25 18:44 UTC (permalink / raw)
  To: Joseph Myers; +Cc: gcc-patches

Joseph, do you have any more comments on the rest of the most recent
revision of the patch?

https://gcc.gnu.org/pipermail/gcc-patches/2020-August/552266.html

Martin

On 8/19/20 6:09 PM, Joseph Myers wrote:
> On Wed, 19 Aug 2020, Martin Sebor via Gcc-patches wrote:
> 
>>> I think you need a while loop there, not just an if, to account for the
>>> case of multiple consecutive cdk_attrs.  At least the GNU attribute syntax
>>>
>>>      direct-declarator:
>>> [...]
>>>        ( gnu-attributes[opt] declarator )
>>>
>>> should produce multiple consecutive cdk_attrs for each level of
>>> parentheses with attributes inside.
>>
>> I had considered a loop but couldn't find a way to trigger what you
>> describe (or a test in the testsuite that would do it) so I didn't
>> use one.  I saw loops like that in other places but I couldn't get
>> even those to uncover such a test case.  Here's what I tried:
>>
>>    #define A(N) __attribute__ ((aligned (N), may_alias))
>>    int n;
>>    void f (int (* A (2) A (4) (* A (2) A (4) (* A (2) A (4) [n])[n])));
>>
>> Sequences of consecutive attributes are all chained together.
>>
>> I've added the loop here but I have no test for it.  It would be
>> good to add one if it really is needed.
> 
> The sort of thing I'm thinking of would be, where A is some attribute:
> 
> void f (int (A (A (A arg))));
> 
> (that example doesn't involve an array, but it illustrates the syntax I'd
> expect to produce multiple consecutive cdk_attrs).
> 


^ permalink raw reply	[flat|nested] 33+ messages in thread

* [PING][PATCH 2/5] C front end support to detect out-of-bounds accesses to array parameters
  2020-08-25 18:44                 ` Martin Sebor
@ 2020-09-03  0:03                   ` Martin Sebor
  2020-09-09 21:39                     ` [PING 2][PATCH " Martin Sebor
  0 siblings, 1 reply; 33+ messages in thread
From: Martin Sebor @ 2020-09-03  0:03 UTC (permalink / raw)
  To: Joseph Myers; +Cc: gcc-patches

Ping: https://gcc.gnu.org/pipermail/gcc-patches/2020-August/552266.html

On 8/25/20 12:44 PM, Martin Sebor wrote:
> Joseph, do you have any more comments on the rest of the most recent
> revision of the patch?
> 
> https://gcc.gnu.org/pipermail/gcc-patches/2020-August/552266.html
> 
> Martin
> 
> On 8/19/20 6:09 PM, Joseph Myers wrote:
>> On Wed, 19 Aug 2020, Martin Sebor via Gcc-patches wrote:
>>
>>>> I think you need a while loop there, not just an if, to account for the
>>>> case of multiple consecutive cdk_attrs.  At least the GNU attribute 
>>>> syntax
>>>>
>>>>      direct-declarator:
>>>> [...]
>>>>        ( gnu-attributes[opt] declarator )
>>>>
>>>> should produce multiple consecutive cdk_attrs for each level of
>>>> parentheses with attributes inside.
>>>
>>> I had considered a loop but couldn't find a way to trigger what you
>>> describe (or a test in the testsuite that would do it) so I didn't
>>> use one.  I saw loops like that in other places but I couldn't get
>>> even those to uncover such a test case.  Here's what I tried:
>>>
>>>    #define A(N) __attribute__ ((aligned (N), may_alias))
>>>    int n;
>>>    void f (int (* A (2) A (4) (* A (2) A (4) (* A (2) A (4) [n])[n])));
>>>
>>> Sequences of consecutive attributes are all chained together.
>>>
>>> I've added the loop here but I have no test for it.  It would be
>>> good to add one if it really is needed.
>>
>> The sort of thing I'm thinking of would be, where A is some attribute:
>>
>> void f (int (A (A (A arg))));
>>
>> (that example doesn't involve an array, but it illustrates the syntax I'd
>> expect to produce multiple consecutive cdk_attrs).
>>
> 


^ permalink raw reply	[flat|nested] 33+ messages in thread

* [PING 2][PATCH 2/5] C front end support to detect out-of-bounds accesses to array parameters
  2020-09-03  0:03                   ` [PING][PATCH " Martin Sebor
@ 2020-09-09 21:39                     ` Martin Sebor
  2020-09-15 23:02                       ` Joseph Myers
  0 siblings, 1 reply; 33+ messages in thread
From: Martin Sebor @ 2020-09-09 21:39 UTC (permalink / raw)
  To: Joseph Myers; +Cc: gcc-patches

Joseph, do you have any concerns with or comments on the most
recent patch or is it okay as is?

https://gcc.gnu.org/pipermail/gcc-patches/2020-August/552266.html

Martin

On 9/2/20 6:03 PM, Martin Sebor wrote:
> Ping: https://gcc.gnu.org/pipermail/gcc-patches/2020-August/552266.html
> 
> On 8/25/20 12:44 PM, Martin Sebor wrote:
>> Joseph, do you have any more comments on the rest of the most recent
>> revision of the patch?
>>
>> https://gcc.gnu.org/pipermail/gcc-patches/2020-August/552266.html
>>
>> Martin
>>
>> On 8/19/20 6:09 PM, Joseph Myers wrote:
>>> On Wed, 19 Aug 2020, Martin Sebor via Gcc-patches wrote:
>>>
>>>>> I think you need a while loop there, not just an if, to account for 
>>>>> the
>>>>> case of multiple consecutive cdk_attrs.  At least the GNU attribute 
>>>>> syntax
>>>>>
>>>>>      direct-declarator:
>>>>> [...]
>>>>>        ( gnu-attributes[opt] declarator )
>>>>>
>>>>> should produce multiple consecutive cdk_attrs for each level of
>>>>> parentheses with attributes inside.
>>>>
>>>> I had considered a loop but couldn't find a way to trigger what you
>>>> describe (or a test in the testsuite that would do it) so I didn't
>>>> use one.  I saw loops like that in other places but I couldn't get
>>>> even those to uncover such a test case.  Here's what I tried:
>>>>
>>>>    #define A(N) __attribute__ ((aligned (N), may_alias))
>>>>    int n;
>>>>    void f (int (* A (2) A (4) (* A (2) A (4) (* A (2) A (4) [n])[n])));
>>>>
>>>> Sequences of consecutive attributes are all chained together.
>>>>
>>>> I've added the loop here but I have no test for it.  It would be
>>>> good to add one if it really is needed.
>>>
>>> The sort of thing I'm thinking of would be, where A is some attribute:
>>>
>>> void f (int (A (A (A arg))));
>>>
>>> (that example doesn't involve an array, but it illustrates the syntax 
>>> I'd
>>> expect to produce multiple consecutive cdk_attrs).
>>>
>>
> 


^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PING 2][PATCH 2/5] C front end support to detect out-of-bounds accesses to array parameters
  2020-09-09 21:39                     ` [PING 2][PATCH " Martin Sebor
@ 2020-09-15 23:02                       ` Joseph Myers
  2020-09-16 19:14                         ` Martin Sebor
  0 siblings, 1 reply; 33+ messages in thread
From: Joseph Myers @ 2020-09-15 23:02 UTC (permalink / raw)
  To: Martin Sebor; +Cc: gcc-patches

On Wed, 9 Sep 2020, Martin Sebor via Gcc-patches wrote:

> Joseph, do you have any concerns with or comments on the most
> recent patch or is it okay as is?
> 
> https://gcc.gnu.org/pipermail/gcc-patches/2020-August/552266.html

I'm not yet convinced by the logic for extracting an array bound from a 
parameter declared using a typedef for an array type.

Say you have

typedef int A[3];

void f (A *x[*]);

so an argument that is an array, using [*], of pointers to arrays, where 
those latter arrays are specified using the typedef.  As I read the logic, 
first the pointer declarator is handled (ignored), then the array 
declarator results in [*] being stored in spec, then the "if (pd->kind == 
cdk_id)" handling comes into play - and because spec is "*" and vbchain is 
NULL_TREE, the upper bound of A gets extracted, but the upper bound of A 
should be irrelevant here because it's a type that's the target of a 
pointer.  The information from parm->specs->type logically comes before, 
not after, the information from the declarator.

As far as I can see, if one declaration gets part of the parameter type 
(involving VLAs) from a typedef and another declaration gets that part of 
the type directly in the declaration, the two spec strings constructed 
might differ in the number of VLA bounds mentioned in the spec strings.  
Is the code using those strings robust to handling the case where some of 
the VLA bounds are missing because they came from a typedef?

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PING 2][PATCH 2/5] C front end support to detect out-of-bounds accesses to array parameters
  2020-09-15 23:02                       ` Joseph Myers
@ 2020-09-16 19:14                         ` Martin Sebor
  2020-09-17 22:38                           ` Joseph Myers
  0 siblings, 1 reply; 33+ messages in thread
From: Martin Sebor @ 2020-09-16 19:14 UTC (permalink / raw)
  To: Joseph Myers; +Cc: gcc-patches

[-- Attachment #1: Type: text/plain, Size: 2257 bytes --]

On 9/15/20 5:02 PM, Joseph Myers wrote:
> On Wed, 9 Sep 2020, Martin Sebor via Gcc-patches wrote:
> 
>> Joseph, do you have any concerns with or comments on the most
>> recent patch or is it okay as is?
>>
>> https://gcc.gnu.org/pipermail/gcc-patches/2020-August/552266.html
> 
> I'm not yet convinced by the logic for extracting an array bound from a
> parameter declared using a typedef for an array type.
> 
> Say you have
> 
> typedef int A[3];
> 
> void f (A *x[*]);
> 
> so an argument that is an array, using [*], of pointers to arrays, where
> those latter arrays are specified using the typedef.  As I read the logic,
> first the pointer declarator is handled (ignored), then the array
> declarator results in [*] being stored in spec, then the "if (pd->kind ==
> cdk_id)" handling comes into play - and because spec is "*" and vbchain is
> NULL_TREE, the upper bound of A gets extracted, but the upper bound of A
> should be irrelevant here because it's a type that's the target of a
> pointer.  The information from parm->specs->type logically comes before,
> not after, the information from the declarator.

I see, you're right.  These arrays of pointers and pointers to arrays
are going to be the death of me...  Thanks for the test case.  I've
tweaked the function some more to handle it and added it to the test
suite.

> 
> As far as I can see, if one declaration gets part of the parameter type
> (involving VLAs) from a typedef and another declaration gets that part of
> the type directly in the declaration, the two spec strings constructed
> might differ in the number of VLA bounds mentioned in the spec strings.
> Is the code using those strings robust to handling the case where some of
> the VLA bounds are missing because they came from a typedef?

Ugh.  I'd completely forgotten those are possible.  I've added code
to the function to handle those as well, and a new test to verify.
Hopefully I didn't open a can of worms by doing that.

Attached is an updated revision of the patch.  Besides the tweaks
above it also contains a cosmetic change to the warning issued
for mismatches in unspecified VLA bounds: it points at the decl
with more of them to guide the user to specify them rather than
make them all unspecified.

Martin

[-- Attachment #2: gcc-50584-2.diff --]
[-- Type: text/x-patch, Size: 91109 bytes --]

[2/5] - C front end support to detect out-of-bounds accesses to array parameters.

gcc/c-family/ChangeLog:

	PR c/50584
	* c-common.h (warn_parm_array_mismatch): Declare new function.
	(has_attribute): Move declaration of an existing function.
	(build_attr_access_from_parms): Declare new function.
	* c-warn.c (parm_array_as_string): Define new function.
	(plus_one):  Define new function.
	(warn_parm_ptrarray_mismatch): Define new function.
	(warn_parm_array_mismatch):  Define new function.
	(vla_bound_parm_decl): New function.
	* c.opt (-Warray-parameter, -Wvla-parameter): New options.
	* c-pretty-print.c (pp_c_type_qualifier_list): Don't print array type
	qualifiers here...
	(c_pretty_printer::direct_abstract_declarator): ...but instead print
	them in brackets here.  Also print [static].  Strip extraneous
	expressions from VLA bounds.

gcc/c/ChangeLog:

	PR c/50584
	* c-decl.c (lookup_last_decl): Define new function.
	(c_decl_attributes): Call it.
	(start_decl): Add argument and use it.
	(finish_decl): Call build_attr_access_from_parms and decl_attributes.
	(get_parm_array_spec): Define new function.
	(push_parm_decl): Call get_parm_array_spec.
	(start_function): Call warn_parm_array_mismatch.  Build attribute
	access and add it to current function.
	* c-parser.c (c_parser_declaration_or_fndef): Diagnose mismatches
	in forms of array parameters.
	* c-tree.h (start_decl): Add argument.

gcc/ChangeLog:

	PR c/50584
	* calls.c (initialize_argument_information): Remove assertion.
	* doc/invoke.texi (-Warray-parameter, -Wvla-parameter): Document.
	* tree-pretty-print.c (dump_generic_node): Correct handling of
	qualifiers.

gcc/testsuite/ChangeLog:

	PR c/50584
	* c-c++-common/Warray-bounds-6.c: Correct C++ declaration, adjust
	text of expected diagnostics.
	* gcc.dg/Wbuiltin-declaration-mismatch-9.c: Prune expected warning.
	* gcc.dg/Warray-parameter-2.c: New test.
	* gcc.dg/Warray-parameter-3.c: New test.
	* gcc.dg/Warray-parameter-4.c: New test.
	* gcc.dg/Warray-parameter-5.c: New test.
	* gcc.dg/Warray-parameter.c: New test.
	* gcc.dg/Wvla-parameter-2.c: New test.
	* gcc.dg/Wvla-parameter-3.c: New test.
	* gcc.dg/Wvla-parameter-4.c: New test.
	* gcc.dg/Wvla-parameter.c: New test.

diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index 4fc64bc4aa6..a61b25f95f1 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -1321,6 +1321,7 @@ extern void c_do_switch_warnings (splay_tree, location_t, tree, tree, bool);
 extern void warn_for_omitted_condop (location_t, tree);
 extern bool warn_for_restrict (unsigned, tree *, unsigned);
 extern void warn_for_address_or_pointer_of_packed_member (tree, tree);
+extern void warn_parm_array_mismatch (location_t, tree, tree);
 
 /* Places where an lvalue, or modifiable lvalue, may be required.
    Used to select diagnostic messages in lvalue_error and
@@ -1375,6 +1376,8 @@ extern tree find_tm_attribute (tree);
 extern const struct attribute_spec::exclusions attr_cold_hot_exclusions[];
 extern const struct attribute_spec::exclusions attr_noreturn_exclusions[];
 extern tree handle_noreturn_attribute (tree *, tree, tree, int, bool *);
+extern bool has_attribute (location_t, tree, tree, tree (*)(tree));
+extern tree build_attr_access_from_parms (tree, bool);
 
 /* In c-format.c.  */
 extern bool valid_format_string_type_p (tree);
@@ -1403,8 +1406,6 @@ extern void maybe_suggest_missing_token_insertion (rich_location *richloc,
 						   location_t prev_token_loc);
 extern tree braced_lists_to_strings (tree, tree);
 
-extern bool has_attribute (location_t, tree, tree, tree (*)(tree));
-
 #if CHECKING_P
 namespace selftest {
   /* Declarations for specific families of tests within c-family,
diff --git a/gcc/c-family/c-pretty-print.c b/gcc/c-family/c-pretty-print.c
index ec0bafe1010..03c89385386 100644
--- a/gcc/c-family/c-pretty-print.c
+++ b/gcc/c-family/c-pretty-print.c
@@ -248,9 +248,12 @@ pp_c_type_qualifier_list (c_pretty_printer *pp, tree t)
   if (!TYPE_P (t))
     t = TREE_TYPE (t);
 
-  qualifiers = TYPE_QUALS (t);
-  pp_c_cv_qualifiers (pp, qualifiers,
-		      TREE_CODE (t) == FUNCTION_TYPE);
+  if (TREE_CODE (t) != ARRAY_TYPE)
+    {
+      qualifiers = TYPE_QUALS (t);
+      pp_c_cv_qualifiers (pp, qualifiers,
+			  TREE_CODE (t) == FUNCTION_TYPE);
+    }
 
   if (!ADDR_SPACE_GENERIC_P (TYPE_ADDR_SPACE (t)))
     {
@@ -572,6 +575,8 @@ c_pretty_printer::abstract_declarator (tree t)
 void
 c_pretty_printer::direct_abstract_declarator (tree t)
 {
+  bool add_space = false;
+
   switch (TREE_CODE (t))
     {
     case POINTER_TYPE:
@@ -585,17 +590,65 @@ c_pretty_printer::direct_abstract_declarator (tree t)
 
     case ARRAY_TYPE:
       pp_c_left_bracket (this);
+
+      if (int quals = TYPE_QUALS (t))
+	{
+	  /* Print the array qualifiers such as in "T[const restrict 3]".  */
+	  pp_c_cv_qualifiers (this, quals, false);
+	  add_space = true;
+	}
+
+      if (tree arr = lookup_attribute ("array", TYPE_ATTRIBUTES (t)))
+	{
+	  if (TREE_VALUE (arr))
+	    {
+	      /* Print the specifier as in "T[static 3]" that's not actually
+		 part of the type but may be added by the front end.  */
+	      pp_c_ws_string (this, "static");
+	      add_space = true;
+	    }
+	  else if (!TYPE_DOMAIN (t))
+	    /* For arrays of unspecified bound using the [*] notation. */
+	    pp_character (this, '*');
+	}
+
       if (tree dom = TYPE_DOMAIN (t))
 	{
 	  if (tree maxval = TYPE_MAX_VALUE (dom))
 	    {
+	      if (add_space)
+		pp_space (this);
+
 	      tree type = TREE_TYPE (maxval);
 
 	      if (tree_fits_shwi_p (maxval))
 		pp_wide_integer (this, tree_to_shwi (maxval) + 1);
-	      else
+	      else if (TREE_CODE (maxval) == INTEGER_CST)
 		expression (fold_build2 (PLUS_EXPR, type, maxval,
 					 build_int_cst (type, 1)));
+	      else
+		{
+		  /* Strip the expressions from around a VLA bound added
+		     internally to make it fit the domain mold, including
+		     any casts.  */
+		  if (TREE_CODE (maxval) == NOP_EXPR)
+		    maxval = TREE_OPERAND (maxval, 0);
+		  if (TREE_CODE (maxval) == PLUS_EXPR
+		      && integer_all_onesp (TREE_OPERAND (maxval, 1)))
+		    {
+		      maxval = TREE_OPERAND (maxval, 0);
+		      if (TREE_CODE (maxval) == NOP_EXPR)
+			maxval = TREE_OPERAND (maxval, 0);
+		    }
+		  if (TREE_CODE (maxval) == SAVE_EXPR)
+		    {
+		      maxval = TREE_OPERAND (maxval, 0);
+		      if (TREE_CODE (maxval) == NOP_EXPR)
+			maxval = TREE_OPERAND (maxval, 0);
+		    }
+
+		  expression (maxval);
+		}
 	    }
 	  else if (TYPE_SIZE (t))
 	    /* Print zero for zero-length arrays but not for flexible
diff --git a/gcc/c-family/c-warn.c b/gcc/c-family/c-warn.c
index fea8885bf35..cd19c568168 100644
--- a/gcc/c-family/c-warn.c
+++ b/gcc/c-family/c-warn.c
@@ -3111,6 +3111,200 @@ plus_one (tree expr)
   return fold_build2 (PLUS_EXPR, type, expr, build_int_cst (type, 1));
 }
 
+/* Try to strip the expressions from around a VLA bound added internally
+   to make it fit the domain mold, including any casts, and return
+   the result.  The goal is to obtain the PARM_DECL the VLA bound may
+   refer to.  */
+
+static tree
+vla_bound_parm_decl (tree expr)
+{
+  if (!expr)
+    return NULL_TREE;
+
+  if (TREE_CODE (expr) == NOP_EXPR)
+    expr = TREE_OPERAND (expr, 0);
+  if (TREE_CODE (expr) == PLUS_EXPR
+      && integer_all_onesp (TREE_OPERAND (expr, 1)))
+    {
+      expr = TREE_OPERAND (expr, 0);
+      if (TREE_CODE (expr) == NOP_EXPR)
+	expr = TREE_OPERAND (expr, 0);
+    }
+  if (TREE_CODE (expr) == SAVE_EXPR)
+    {
+      expr = TREE_OPERAND (expr, 0);
+      if (TREE_CODE (expr) == NOP_EXPR)
+	expr = TREE_OPERAND (expr, 0);
+    }
+  return expr;
+}
+
+/* Diagnose mismatches in VLA bounds between function parameters NEWPARMS
+   of pointer types on a redeclaration os a function previously declared
+   with CURPARMS at ORIGLOC.  */
+
+static void
+warn_parm_ptrarray_mismatch (location_t origloc, tree curparms, tree newparms)
+{
+  /* Maps each named integral parameter seen so far to its position
+     in the argument list; used to associate VLA sizes with arguments.  */
+  hash_map<tree, unsigned> curparm2pos;
+  hash_map<tree, unsigned> newparm2pos;
+
+  unsigned parmpos = 1;
+  for (tree curp = curparms, newp = newparms; curp && newp;
+       curp = TREE_CHAIN (curp), newp = TREE_CHAIN (newp), ++parmpos)
+    {
+      tree curtyp = TREE_TYPE (curp), newtyp = TREE_TYPE (newp);
+      if (INTEGRAL_TYPE_P (curtyp))
+	{
+	  /* Only add named parameters; unnamed ones cannot be referred
+	     to in VLA bounds.  */
+	  if (DECL_NAME (curp))
+	    curparm2pos.put (curp, parmpos);
+	  if (DECL_NAME (newp))
+	    newparm2pos.put (newp, parmpos);
+
+	  continue;
+	}
+
+      /* The parameter types should match at this point so only test one.  */
+      if (TREE_CODE (curtyp) != POINTER_TYPE)
+	continue;
+
+      do
+	{
+	  curtyp = TREE_TYPE (curtyp);
+	  newtyp = TREE_TYPE (newtyp);
+	}
+      while (TREE_CODE (curtyp) == POINTER_TYPE
+	     && TREE_CODE (newtyp) == POINTER_TYPE);
+
+      if (TREE_CODE (curtyp) != ARRAY_TYPE
+	  || TREE_CODE (newtyp) != ARRAY_TYPE)
+	{
+	  if (curtyp == error_mark_node
+	      || newtyp == error_mark_node)
+	    return;
+
+	  continue;
+	}
+
+      tree curdom = TYPE_DOMAIN (curtyp), newdom = TYPE_DOMAIN (newtyp);
+      tree curbnd = curdom ? TYPE_MAX_VALUE (curdom) : NULL_TREE;
+      tree newbnd = newdom ? TYPE_MAX_VALUE (newdom) : NULL_TREE;
+
+      if (DECL_P (curp))
+	origloc = DECL_SOURCE_LOCATION (curp);
+      else if (EXPR_P (curp) && EXPR_HAS_LOCATION (curp))
+	origloc = EXPR_LOCATION (curp);
+
+      /* The location of the parameter in the current redeclaration.  */
+      location_t newloc = DECL_SOURCE_LOCATION (newp);
+      if (origloc == UNKNOWN_LOCATION)
+	origloc = newloc;
+
+      /* Issue -Warray-parameter onless one or more mismatches involves
+	 a VLA bound; then issue -Wvla-parameter.  */
+      int opt = OPT_Warray_parameter_;
+      /* Traverse the two array types looking for variable bounds and
+	 comparing the two in each pair for mismatches either in their
+	 positions in the function parameter list or lexicographically
+	 for others.  Record the 1-based parameter position of each
+	 mismatch in BNDVEC, and the location of each parameter in
+	 the mismatch in WARNLOC (for the new parameter list) and
+	 NOTELOC (for the current parameter list).  */
+      unsigned bndpos = 1;
+      auto_vec<unsigned> bndvec;
+      gcc_rich_location warnloc (newloc);
+      gcc_rich_location noteloc (origloc);
+      for ( ; curtyp || newtyp;
+	    ++bndpos,
+	      curbnd = curdom ? TYPE_MAX_VALUE (curdom) : NULL_TREE,
+	      newbnd = newdom ? TYPE_MAX_VALUE (newdom) : NULL_TREE)
+	{
+	  /* Try to strip each bound down to the PARM_DECL if it does
+	     correspond to one.  Either bound can be null if it's
+	     unspecified (i.e., has the [*] form).  */
+	  curbnd = vla_bound_parm_decl (curbnd);
+	  newbnd = vla_bound_parm_decl (newbnd);
+
+	  /* Peel the current bound off CURTYP and NEWTYP, skipping
+	     over any subsequent pointer types.  */
+	  if (curtyp && TREE_CODE (curtyp) == ARRAY_TYPE)
+	    {
+	      do
+		curtyp = TREE_TYPE (curtyp);
+	      while (TREE_CODE (curtyp) == POINTER_TYPE);
+	      if (TREE_CODE (curtyp) == ARRAY_TYPE)
+		curdom = TYPE_DOMAIN (curtyp);
+	      else
+		curdom = NULL_TREE;
+	    }
+	  else
+	    curtyp = NULL_TREE;
+
+	  if (newtyp && TREE_CODE (newtyp) == ARRAY_TYPE)
+	    {
+	      do
+		newtyp = TREE_TYPE (newtyp);
+	      while (TREE_CODE (newtyp) == POINTER_TYPE);
+	      if (TREE_CODE (newtyp) == ARRAY_TYPE)
+		newdom = TYPE_DOMAIN (newtyp);
+	      else
+		newdom = NULL_TREE;
+	    }
+	  else
+	    newtyp = NULL_TREE;
+
+	  /* Move on to the next bound if this one is unspecified.  */
+	  if (!curbnd && !newbnd)
+	    continue;
+
+	  /* Try to find each bound in the parameter list.  */
+	  const unsigned* const pcurbndpos = curparm2pos.get (curbnd);
+	  const unsigned* const pnewbndpos = newparm2pos.get (newbnd);
+	  /* Move on if both bounds refer to the same parameter.  */
+	  if (pcurbndpos && pnewbndpos && *pcurbndpos == *pnewbndpos)
+	    continue;
+
+	  /* Move on if the bounds look the same.  */
+	  if (!pcurbndpos && !pnewbndpos
+	      && curbnd && newbnd
+	      && operand_equal_p (curbnd, newbnd, OEP_LEXICOGRAPHIC))
+	    continue;
+
+	  if ((curbnd && TREE_CODE (curbnd) != INTEGER_CST)
+	      || (newbnd && TREE_CODE (newbnd) != INTEGER_CST))
+	    opt = OPT_Wvla_parameter;
+
+	  /* Record the mismatch.  */
+	  bndvec.safe_push (bndpos);
+	  /* Underline the bounding parameter in the declaration.  */
+	  if (curbnd && TREE_CODE (curbnd) == PARM_DECL)
+	    noteloc.add_range (DECL_SOURCE_LOCATION (curbnd));
+	  if (newbnd && TREE_CODE (newbnd) == PARM_DECL)
+	    warnloc.add_range (DECL_SOURCE_LOCATION (newbnd));
+	}
+
+      const unsigned nbnds = bndvec.length ();
+      if (!nbnds)
+	continue;
+
+      /* Use attr_access to format the parameter types.  */
+      attr_access spec = { };
+      const std::string newparmstr = spec.array_as_string (TREE_TYPE (newp));
+      const std::string curparmstr = spec.array_as_string (TREE_TYPE (curp));
+
+      if (warning_n (&warnloc, opt, nbnds,
+		     "mismatch in bound %Z of argument %u declared as %s",
+		     "mismatch in bounds %Z of argument %u declared as %s",
+		     bndvec.address (), nbnds, parmpos, newparmstr.c_str ()))
+	inform (&noteloc, "previously declared as %s",	curparmstr.c_str ());
+    }
+}
+
 /* Detect and diagnose a mismatch between an attribute access specification
    on the original declaration of FNDECL and that on the parameters NEWPARMS
    from its refeclaration.  ORIGLOC is the location of the first declaration
@@ -3146,11 +3340,15 @@ warn_parm_array_mismatch (location_t origloc, tree fndecl, tree newparms)
   rdwr_map new_idx;
   init_attr_rdwr_indices (&new_idx, newattrs);
 
-  /* If both are empty there's nothing to do.  If at least one isn't
-     empty there may be mismatches, such as between f(T*) and f(T[1]),
-     where the former mapping woud be empty.  */
   if (cur_idx.is_empty () && new_idx.is_empty ())
-    return;
+    {
+      /* If both specs are empty check pointers to VLAs for mismatches. */
+      warn_parm_ptrarray_mismatch (origloc, curparms, newparms);
+      return;
+    }
+  /* ...otherwise, if at least one spec isn't empty there may be mismatches,
+     such as  between f(T*) and f(T[1]), where the former mapping woud be
+     empty.  */
 
   /* Create an empty access specification and use it for pointers with
      no spec of their own.  */
@@ -3222,13 +3420,13 @@ warn_parm_array_mismatch (location_t origloc, tree fndecl, tree newparms)
       if (new_vla_p && !cur_vla_p)
 	{
 	  if (warning_at (newloc, OPT_Wvla_parameter,
-			  "argument %u of type %qs declared as "
+			  "argument %u of type %s declared as "
 			  "a variable length array",
 			  parmpos + 1, newparmstr.c_str ()))
 	    inform (origloc,
 		    (cura == &ptr_spec
-		     ? G_("previously declared as a pointer %qs")
-		     : G_("previously declared as an ordinary array %qs")),
+		     ? G_("previously declared as a pointer %s")
+		     : G_("previously declared as an ordinary array %s")),
 		    curparmstr.c_str ());
 	  continue;
 	}
@@ -3241,11 +3439,11 @@ warn_parm_array_mismatch (location_t origloc, tree fndecl, tree newparms)
 	    {
 	      /* Diagnose a pointer/VLA mismatch.  */
 	      if (warning_at (newloc, OPT_Wvla_parameter,
-			      "argument %u of type %qs declared "
+			      "argument %u of type %s declared "
 			      "as a pointer",
 			      parmpos + 1, newparmstr.c_str ()))
 		inform (origloc,
-			"previously declared as a variable length array %qs",
+			"previously declared as a variable length array %s",
 			curparmstr.c_str ());
 	      continue;
 	    }
@@ -3255,10 +3453,10 @@ warn_parm_array_mismatch (location_t origloc, tree fndecl, tree newparms)
 	      /* Diagnose mismatches between arrays with a constant
 		 bound and pointers.  */
 	      if (warning_at (newloc, OPT_Warray_parameter_,
-			      "argument %u of type %qs declared "
+			      "argument %u of type %s declared "
 			      "as a pointer",
 			      parmpos + 1, newparmstr.c_str ()))
-		inform (origloc, "previously declared as an array %qs",
+		inform (origloc, "previously declared as an array %s",
 			curparmstr.c_str ());
 	      continue;
 	    }
@@ -3267,11 +3465,11 @@ warn_parm_array_mismatch (location_t origloc, tree fndecl, tree newparms)
       if (!new_vla_p && cur_vla_p)
 	{
 	  if (warning_at (newloc, OPT_Wvla_parameter,
-			  "argument %u of type %qs declared "
+			  "argument %u of type %s declared "
 			  "as an ordinary array",
 			  parmpos + 1, newparmstr.c_str ()))
 	    inform (origloc,
-		    "previously declared as a variable length array %qs",
+		    "previously declared as a variable length array %s",
 		    curparmstr.c_str ());
 	  continue;
 	}
@@ -3295,33 +3493,58 @@ warn_parm_array_mismatch (location_t origloc, tree fndecl, tree newparms)
 	  if (newbnds != curbnds)
 	    {
 	      if (warning_n (newloc, OPT_Wvla_parameter, newbnds,
-			     "argument %u of type %qs declared with "
+			     "argument %u of type %s declared with "
 			     "%u variable bound",
-			     "argument %u of type %qs declared with "
+			     "argument %u of type %s declared with "
 			     "%u variable bounds",
 			     parmpos + 1, newparmstr.c_str (),
 			     newbnds))
 		inform_n (origloc, curbnds,
-			  "previously declared as %qs with %u variable bound",
-			  "previously declared as %qs with %u variable bounds",
+			  "previously declared as %s with %u variable bound",
+			  "previously declared as %s with %u variable bounds",
 			  curparmstr.c_str (), curbnds);
 	      continue;
 	    }
 
 	  if (newunspec != curunspec)
 	    {
-	      if (warning_n (newloc, OPT_Wvla_parameter, newunspec,
-			     "argument %u of type %qs declared with "
+	      location_t warnloc = newloc, noteloc = origloc;
+	      const char *warnparmstr = newparmstr.c_str ();
+	      const char *noteparmstr = curparmstr.c_str ();
+	      unsigned warnunspec = newunspec, noteunspec = curunspec;
+
+	      if (newunspec < curunspec)
+		{
+		  /* If the new declaration has fewer unspecified bounds
+		     point the warning to the previous declaration to make
+		     it clear that that's the one to change.  Otherwise,
+		     point it to the new decl.  */
+		  std::swap (warnloc, noteloc);
+		  std::swap (warnparmstr, noteparmstr);
+		  std::swap (warnunspec, noteunspec);
+		}
+	      if (warning_n (warnloc, OPT_Wvla_parameter, warnunspec,
+			     "argument %u of type %s declared with "
 			     "%u unspecified variable bound",
-			     "argument %u of type %qs declared with "
+			     "argument %u of type %s declared with "
 			     "%u unspecified variable bounds",
-			     parmpos + 1, newparmstr.c_str (), newunspec))
-		inform_n (origloc, curunspec,
-			  "previously declared as %qs with %u unspecified "
-			  "variable bound",
-			  "previously declared as %qs with %u unspecified "
-			  "variable bounds",
-			  curparmstr.c_str (), curunspec);
+			     parmpos + 1, warnparmstr, warnunspec))
+		{
+		  if (warnloc == newloc)
+		    inform_n (noteloc, noteunspec,
+			      "previously declared as %s with %u unspecified "
+			      "variable bound",
+			      "previously declared as %s with %u unspecified "
+			      "variable bounds",
+			      noteparmstr, noteunspec);
+		  else
+		    inform_n (noteloc, noteunspec,
+			      "subsequently declared as %s with %u unspecified "
+			      "variable bound",
+			      "subsequently declared as %s with %u unspecified "
+			      "variable bounds",
+			      noteparmstr, noteunspec);
+		}
 	      continue;
 	    }
 	}
@@ -3336,8 +3559,8 @@ warn_parm_array_mismatch (location_t origloc, tree fndecl, tree newparms)
 	  tree newpos = TREE_PURPOSE (newvbl);
 	  tree curpos = TREE_PURPOSE (curvbl);
 
-	  tree newbnd = TREE_VALUE (newvbl);
-	  tree curbnd = TREE_VALUE (curvbl);
+	  tree newbnd = vla_bound_parm_decl (TREE_VALUE (newvbl));
+	  tree curbnd = vla_bound_parm_decl (TREE_VALUE (curvbl));
 
 	  if (newpos == curpos && newbnd == curbnd)
 	    /* In the expected case when both bounds either refer to
@@ -3370,14 +3593,14 @@ warn_parm_array_mismatch (location_t origloc, tree fndecl, tree newparms)
 		  /* Also underline the VLA bound argument.  */
 		  richloc.add_range (DECL_SOURCE_LOCATION (newbnd));
 		  warned = warning_at (&richloc, OPT_Wvla_parameter,
-				       "argument %u of type %qs declared "
+				       "argument %u of type %s declared "
 				       "with mismatched bound argument %E",
 				       parmpos + 1, newparmstr.c_str (),
 				       plus_one (newpos));
 		}
 	      else
 		warned = warning_at (&richloc, OPT_Wvla_parameter,
-				     "argument %u of type %qs declared "
+				     "argument %u of type %s declared "
 				     "with mismatched bound %<%s%>",
 				     parmpos + 1, newparmstr.c_str (),
 				     newbndstr);
@@ -3389,12 +3612,12 @@ warn_parm_array_mismatch (location_t origloc, tree fndecl, tree newparms)
 		    {
 		      /* Also underline the VLA bound argument.  */
 		      richloc.add_range (DECL_SOURCE_LOCATION (curbnd));
-		      inform (&richloc, "previously declared as %qs with "
+		      inform (&richloc, "previously declared as %s with "
 			      "bound argument %E",
 			      curparmstr.c_str (), plus_one (curpos));
 		    }
 		  else
-		    inform (&richloc, "previously declared as %qs with bound "
+		    inform (&richloc, "previously declared as %s with bound "
 			    "%<%s%>", curparmstr.c_str (), curbndstr);
 
 		  continue;
@@ -3410,11 +3633,10 @@ warn_parm_array_mismatch (location_t origloc, tree fndecl, tree newparms)
 		continue;
 
 	      if (warning_at (newloc, OPT_Wvla_parameter,
-			      "argument %u of type %qs declared with "
+			      "argument %u of type %s declared with "
 			      "mismatched bound %<%s%>",
-			      parmpos + 1, newparmstr.c_str (),
-			      newbndstr))
-		inform (origloc, "previously declared as %qs with bound %qs",
+			      parmpos + 1, newparmstr.c_str (), newbndstr))
+		inform (origloc, "previously declared as %s with bound %qs",
 			curparmstr.c_str (), curbndstr);
 	      continue;
 	    }
@@ -3433,8 +3655,8 @@ warn_parm_array_mismatch (location_t origloc, tree fndecl, tree newparms)
 	continue;
 
       if (warning_at (newloc, OPT_Warray_parameter_,
-		      "argument %u of type %qs with mismatched bound",
+		      "argument %u of type %s with mismatched bound",
 		      parmpos + 1, newparmstr.c_str ()))
-	inform (origloc, "previously declared as %qs", curparmstr.c_str ());
+	inform (origloc, "previously declared as %s", curparmstr.c_str ());
     }
 }
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index 2b1aca16eb4..6f3997405a1 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -338,6 +338,14 @@ Warray-bounds=
 LangEnabledBy(C ObjC C++ LTO ObjC++,Wall,1,0)
 ; in common.opt
 
+Warray-parameter
+C ObjC C++ ObjC++ Warning Alias(Warray-parameter=, 2, 0)
+Warn about mismatched declarations of array parameters and unsafe accesses to them.
+
+Warray-parameter=
+C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_array_parameter) IntegerRange(0, 2) LangEnabledBy(C ObjC C++ ObjC++,Wall, 2, 0) Warning
+Warn about mismatched declarations of array parameters and unsafe accesses to them.
+
 Wzero-length-bounds
 C ObjC C++ ObjC++ Var(warn_zero_length_bounds) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall)
 Warn about accesses to interior zero-length array members.
@@ -1253,6 +1261,10 @@ Wno-vla-larger-than
 C ObjC C++ LTO ObjC++ Alias(Wvla-larger-than=,18446744073709551615EiB,none) Warning
 Disable Wvla-larger-than= warning.  Equivalent to Wvla-larger-than=<SIZE_MAX> or larger.
 
+Wvla-parameter
+C ObjC C++ ObjC++ Var(warn_vla_parameter) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall)
+Warn about mismatched declarations of VLA parameters.
+
 Wvolatile
 C++ ObjC++ Var(warn_volatile) Warning
 Warn about deprecated uses of volatile qualifier.
diff --git a/gcc/c/c-decl.c b/gcc/c/c-decl.c
index 5d6b504fe78..2aba39d779c 100644
--- a/gcc/c/c-decl.c
+++ b/gcc/c/c-decl.c
@@ -25,6 +25,7 @@ along with GCC; see the file COPYING3.  If not see
    line numbers.  For example, the CONST_DECLs for enum values.  */
 
 #include "config.h"
+#define INCLUDE_STRING
 #define INCLUDE_UNIQUE_PTR
 #include "system.h"
 #include "coretypes.h"
@@ -58,6 +59,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "c-family/known-headers.h"
 #include "c-family/c-spellcheck.h"
 
+#include "tree-pretty-print.h"
+
 /* In grokdeclarator, distinguish syntactic contexts of declarators.  */
 enum decl_context
 { NORMAL,			/* Ordinary declaration */
@@ -4967,6 +4970,17 @@ groktypename (struct c_type_name *type_name, tree *expr,
   return type;
 }
 
+/* Looks up the most recent pushed declaration corresponding to DECL.  */
+
+static tree
+lookup_last_decl (tree decl)
+{
+  tree last_decl = lookup_name (DECL_NAME (decl));
+  if (!last_decl)
+    last_decl = lookup_name_in_scope (DECL_NAME (decl), external_scope);
+  return last_decl;
+}
+
 /* Wrapper for decl_attributes that adds some implicit attributes
    to VAR_DECLs or FUNCTION_DECLs.  */
 
@@ -4995,10 +5009,7 @@ c_decl_attributes (tree *node, tree attributes, int flags)
      so far so that attributes on the current declaration that's
      about to be pushed that conflict with the former can be detected,
      diagnosed, and rejected as appropriate.  */
-  tree last_decl = lookup_name (DECL_NAME (*node));
-  if (!last_decl)
-    last_decl = lookup_name_in_scope (DECL_NAME (*node), external_scope);
-
+  tree last_decl = lookup_last_decl (*node);
   return decl_attributes (node, attributes, flags, last_decl);
 }
 
@@ -5008,6 +5019,8 @@ c_decl_attributes (tree *node, tree attributes, int flags)
    have been parsed, before parsing the initializer if any.
    Here we create the ..._DECL node, fill in its type,
    and put it on the list of decls for the current context.
+   When nonnull, set *LASTLOC to the location of the prior declaration
+   of the same entity if one exists.
    The ..._DECL node is returned as the value.
 
    Exception: for arrays where the length is not specified,
@@ -5020,7 +5033,7 @@ c_decl_attributes (tree *node, tree attributes, int flags)
 
 tree
 start_decl (struct c_declarator *declarator, struct c_declspecs *declspecs,
-	    bool initialized, tree attributes)
+	    bool initialized, tree attributes, location_t *lastloc /* = NULL */)
 {
   tree decl;
   tree tem;
@@ -5038,6 +5051,10 @@ start_decl (struct c_declarator *declarator, struct c_declspecs *declspecs,
   if (!decl || decl == error_mark_node)
     return NULL_TREE;
 
+  if (tree lastdecl = lastloc ? lookup_last_decl (decl) : NULL_TREE)
+    if (lastdecl != error_mark_node)
+      *lastloc = DECL_SOURCE_LOCATION (lastdecl);
+
   if (expr)
     add_stmt (fold_convert (void_type_node, expr));
 
@@ -5475,6 +5492,14 @@ finish_decl (tree decl, location_t init_loc, tree init,
 	  if (asmspec && VAR_P (decl) && C_DECL_REGISTER (decl))
 	    DECL_HARD_REGISTER (decl) = 1;
 	  rest_of_decl_compilation (decl, true, 0);
+
+	  if (TREE_CODE (decl) == FUNCTION_DECL)
+	    {
+	      tree parms = DECL_ARGUMENTS (decl);
+	      const bool builtin = fndecl_built_in_p (decl);
+	      if (tree access = build_attr_access_from_parms (parms, !builtin))
+		decl_attributes (&decl, access, 0);
+	    }
 	}
       else
 	{
@@ -5627,6 +5652,175 @@ grokparm (const struct c_parm *parm, tree *expr)
   return decl;
 }
 
+/* Return attribute "arg spec" corresponding to an array/VLA parameter
+   described by PARM, concatenated onto attributes ATTRS.
+   The spec consists of one dollar symbol for each specified variable
+   bound, one asterisk for each unspecified variable bound, followed
+   by at most one specification of the most significant bound of
+   an ordinary array parameter.  For ordinary arrays the specification
+   is either the constant bound itself, or the space character for
+   an array with an unspecified bound (the [] form).  Finally, a chain
+   of specified variable bounds is appended to the spec, starting with
+   the most significant bound.  For example, the PARM T a[2][m][3][n]
+   will produce __attribute__((arg spec ("[$$2]", m, n)).
+   For T a typedef for an array with variable bounds, the bounds are
+   included in the specification in the expected order.
+   No "arg spec"  is created for parameters of pointer types, making
+   a distinction between T(*)[N] (or, equivalently, T[][N]) and
+   the T[M][N] form, all of which have the same type and are represented
+   the same, but only the last of which gets an "arg spec" describing
+   the most significant bound M.  */
+
+static tree
+get_parm_array_spec (const struct c_parm *parm, tree attrs)
+{
+  /* The attribute specification string, minor bound first.  */
+  std::string spec;
+
+  /* A list of VLA variable bounds, major first, or null if unspecified
+     or not a VLA.  */
+  tree vbchain = NULL_TREE;
+  /* True for a pointer parameter.  */
+  bool pointer = false;
+  /* True for an ordinary array with an unpecified bound.  */
+  bool nobound = false;
+
+  /* Create a string representation for the bounds of the array/VLA.  */
+  for (c_declarator *pd = parm->declarator, *next; pd; pd = next)
+    {
+      next = pd->declarator;
+      if (next && next->kind == cdk_attrs)
+	next = next->declarator;
+
+      /* Remember if a pointer has been seen to avoid storing the constant
+	 bound.  */
+      if (pd->kind == cdk_pointer)
+	pointer = true;
+
+      if ((pd->kind == cdk_pointer || pd->kind == cdk_function)
+	  && (!next || next->kind == cdk_id))
+	{
+	  /* Do nothing for the common case of a pointer.  The fact that
+	     the parameter is one can be deduced from the absence of
+	     an arg spec for it.  */
+	  return attrs;
+	}
+
+      if (pd->kind == cdk_id)
+	{
+	  if (pointer
+	      || !parm->specs->type
+	      || TREE_CODE (parm->specs->type) != ARRAY_TYPE
+	      || !TYPE_DOMAIN (parm->specs->type)
+	      || !TYPE_MAX_VALUE (TYPE_DOMAIN (parm->specs->type)))
+	    continue;
+
+	  tree max = TYPE_MAX_VALUE (TYPE_DOMAIN (parm->specs->type));
+	  if (!vbchain
+	      && TREE_CODE (max) == INTEGER_CST)
+	    {
+	      /* Extract the upper bound from a parameter of an array type
+		 unless the parameter is an ordinary array of unspecified
+		 bound in which case a next iteration of the loop will
+		 exit.  */
+	      if (spec.empty () || spec.end ()[-1] != ' ')
+		{
+		  if (!tree_fits_shwi_p (max))
+		    continue;
+
+		  /* The upper bound is the value of the largest valid
+		     index.  */
+		  HOST_WIDE_INT n = tree_to_shwi (max) + 1;
+		  char buf[40];
+		  sprintf (buf, "%lu", (unsigned long)n);
+		  spec += buf;
+		}
+	      continue;
+	    }
+
+	  /* For a VLA typedef, create a list of its variable bounds and
+	     append it in the expected order to VBCHAIN.  */
+	  tree tpbnds = NULL_TREE;
+	  for (tree type = parm->specs->type; TREE_CODE (type) == ARRAY_TYPE;
+	       type = TREE_TYPE (type))
+	    {
+	      tree nelts = array_type_nelts (type);
+	      if (TREE_CODE (nelts) != INTEGER_CST)
+		{
+		  /* Each variable VLA bound is represented by the dollar
+		     sign.  */
+		  spec += "$";
+		  tpbnds = tree_cons (NULL_TREE, nelts, tpbnds);
+		}
+	    }
+	  tpbnds = nreverse (tpbnds);
+	  vbchain = chainon (vbchain, tpbnds);
+	  continue;
+	}
+
+      if (pd->kind != cdk_array)
+	continue;
+
+      if (pd->u.array.vla_unspec_p)
+	{
+	  /* Each unspecified bound is represented by a star.  There
+	     can be any number of these in a declaration (but none in
+	     a definition).  */
+	  spec += '*';
+	  continue;
+	}
+
+      tree nelts = pd->u.array.dimen;
+      if (!nelts)
+	{
+	  /* Ordinary array of unspecified size.  There can be at most
+	     one for the most significant bound.  Exit on the next
+	     iteration which determines whether or not PARM is declared
+	     as a pointer or an array.  */
+	  nobound = true;
+	  continue;
+	}
+
+      if (TREE_CODE (nelts) == INTEGER_CST)
+	{
+	  /* Skip all constant bounds except the most significant one.
+	     The interior ones are included in the array type.  */
+	  if (next && (next->kind == cdk_array || next->kind == cdk_pointer))
+	    continue;
+
+	  if (!tree_fits_uhwi_p (nelts))
+	    /* Bail completely on invalid bounds.  */
+	    return attrs;
+
+	  char buf[40];
+	  const char *code = pd->u.array.static_p ? "s" : "";
+	  unsigned HOST_WIDE_INT n = tree_to_uhwi (nelts);
+	  sprintf (buf, "%s%llu", code, (unsigned long long)n);
+	  spec += buf;
+	  break;
+	}
+
+      /* Each variable VLA bound is represented by a dollar sign.  */
+      spec += "$";
+      vbchain = tree_cons (NULL_TREE, nelts, vbchain);
+    }
+
+  if (spec.empty () && !nobound)
+    return attrs;
+
+  spec.insert (0, "[");
+  if (nobound)
+    /* Ordinary array of unspecified bound is represented by a space.
+       It must be last in the spec.  */
+    spec += ' ';
+  spec += ']';
+
+  tree acsstr = build_string (spec.length () + 1, spec.c_str ());
+  tree args = tree_cons (NULL_TREE, acsstr, vbchain);
+  tree name = get_identifier ("arg spec");
+  return tree_cons (name, args, attrs);
+}
+
 /* Given a parsed parameter declaration, decode it into a PARM_DECL
    and push that on the current scope.  EXPR is a pointer to an
    expression that needs to be evaluated for the side effects of array
@@ -5636,12 +5830,12 @@ void
 push_parm_decl (const struct c_parm *parm, tree *expr)
 {
   tree attrs = parm->attrs;
-  tree decl;
-
-  decl = grokdeclarator (parm->declarator, parm->specs, PARM, false, NULL,
-			 &attrs, expr, NULL, DEPRECATED_NORMAL);
+  tree decl = grokdeclarator (parm->declarator, parm->specs, PARM, false, NULL,
+			      &attrs, expr, NULL, DEPRECATED_NORMAL);
   if (decl && DECL_P (decl))
     DECL_SOURCE_LOCATION (decl) = parm->loc;
+
+  attrs = get_parm_array_spec (parm, attrs);
   decl_attributes (&decl, attrs, 0);
 
   decl = pushdecl (decl);
@@ -9227,6 +9421,7 @@ start_function (struct c_declspecs *declspecs, struct c_declarator *declarator,
   old_decl = lookup_name_in_scope (DECL_NAME (decl1), current_scope);
   if (old_decl && TREE_CODE (old_decl) != FUNCTION_DECL)
     old_decl = NULL_TREE;
+
   current_function_prototype_locus = UNKNOWN_LOCATION;
   current_function_prototype_built_in = false;
   current_function_prototype_arg_types = NULL_TREE;
@@ -9357,12 +9552,22 @@ start_function (struct c_declspecs *declspecs, struct c_declarator *declarator,
 		 "%qD is normally a non-static function", decl1);
     }
 
+  tree parms = current_function_arg_info->parms;
+  if (old_decl)
+    {
+      location_t origloc = DECL_SOURCE_LOCATION (old_decl);
+      warn_parm_array_mismatch (origloc, old_decl, parms);
+    }
+
   /* Record the decl so that the function name is defined.
      If we already have a decl for this name, and it is a FUNCTION_DECL,
      use the old decl.  */
 
   current_function_decl = pushdecl (decl1);
 
+  if (tree access = build_attr_access_from_parms (parms, false))
+    decl_attributes (&current_function_decl, access, 0, old_decl);
+
   push_scope ();
   declare_parm_level ();
 
diff --git a/gcc/c/c-parser.c b/gcc/c/c-parser.c
index 7961cbc98bb..f5d18195c14 100644
--- a/gcc/c/c-parser.c
+++ b/gcc/c/c-parser.c
@@ -68,6 +68,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "intl.h"
 #include "c-family/name-hint.h"
 #include "tree-iterator.h"
+#include "tree-pretty-print.h"
 #include "memmodel.h"
 #include "c-family/known-headers.h"
 
@@ -2296,13 +2297,11 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok,
 		  c_parser_skip_to_end_of_block_or_statement (parser);
 		  return;
 		}
-	      tree d = start_decl (declarator, specs, false,
-				   chainon (postfix_attrs,
-					    all_prefix_attrs));
-	      if (d
-		  && TREE_CODE (d) == FUNCTION_DECL
-		  && DECL_ARGUMENTS (d) == NULL_TREE
-		  && DECL_INITIAL (d) == NULL_TREE)
+
+	      location_t lastloc = UNKNOWN_LOCATION;
+	      tree attrs = chainon (postfix_attrs, all_prefix_attrs);
+	      tree d = start_decl (declarator, specs, false, attrs, &lastloc);
+	      if (d && TREE_CODE (d) == FUNCTION_DECL)
 		{
 		  /* Find the innermost declarator that is neither cdk_id
 		     nor cdk_attrs.  */
@@ -2331,10 +2330,18 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok,
 			gcc_unreachable ();
 		      }
 
-		  /* If it exists and is cdk_function, use its parameters.  */
+		  /* If it exists and is cdk_function declaration whose
+		     arguments have not been set yet, use its arguments.  */
 		  if (last_non_id_attrs
 		      && last_non_id_attrs->kind == cdk_function)
-		    DECL_ARGUMENTS (d) = last_non_id_attrs->u.arg_info->parms;
+		    {
+		      tree parms = last_non_id_attrs->u.arg_info->parms;
+		      if (DECL_ARGUMENTS (d) == NULL_TREE
+			  && DECL_INITIAL (d) == NULL_TREE)
+			DECL_ARGUMENTS (d) = parms;
+
+		      warn_parm_array_mismatch (lastloc, d, parms);
+		    }
 		}
 	      if (omp_declare_simd_clauses.exists ())
 		{
@@ -2363,7 +2370,7 @@ c_parser_declaration_or_fndef (c_parser *parser, bool fndef_ok,
 	      if (d)
 		finish_decl (d, UNKNOWN_LOCATION, NULL_TREE,
 			     NULL_TREE, asm_name);
-	      
+
 	      if (c_parser_next_token_is_keyword (parser, RID_IN))
 		{
 		  if (d)
diff --git a/gcc/c/c-tree.h b/gcc/c/c-tree.h
index 10938cf0857..3adc29dafc4 100644
--- a/gcc/c/c-tree.h
+++ b/gcc/c/c-tree.h
@@ -609,7 +609,7 @@ extern void shadow_tag_warned (const struct c_declspecs *, int);
 extern tree start_enum (location_t, struct c_enum_contents *, tree);
 extern bool start_function (struct c_declspecs *, struct c_declarator *, tree);
 extern tree start_decl (struct c_declarator *, struct c_declspecs *, bool,
-			tree);
+			tree, location_t * = NULL);
 extern tree start_struct (location_t, enum tree_code, tree,
 			  class c_struct_parse_info **);
 extern void store_parm_decls (void);
diff --git a/gcc/calls.c b/gcc/calls.c
index 77ab8647a10..79aa2c09b29 100644
--- a/gcc/calls.c
+++ b/gcc/calls.c
@@ -2463,7 +2463,7 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
 	  if (POINTER_TYPE_P (type))
 	    {
 	      access->ptr = args[i].tree_value;
-	      gcc_assert (access->size == NULL_TREE);
+	      // A nonnull ACCESS->SIZE contains VLA bounds.  */
 	    }
 	  else
 	    {
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 70dc1ab73a1..0243d060994 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -5214,6 +5214,7 @@ Options} and @ref{Objective-C and Objective-C++ Dialect Options}.
 
 @gccoptlist{-Waddress   @gol
 -Warray-bounds=1 @r{(only with} @option{-O2}@r{)}  @gol
+-Warray-parameter=2 @r{(C and Objective-C only)} @gol
 -Wbool-compare  @gol
 -Wbool-operation  @gol
 -Wc++11-compat  -Wc++14-compat  @gol
@@ -5265,6 +5266,7 @@ Options} and @ref{Objective-C and Objective-C++ Dialect Options}.
 -Wunused-label     @gol
 -Wunused-value     @gol
 -Wunused-variable  @gol
+-Wvla-parameter @r{(C and Objective-C only)} @gol
 -Wvolatile-register-var  @gol
 -Wzero-length-bounds}
 
@@ -7108,6 +7110,54 @@ pointers. This warning level may give a larger number of
 false positives and is deactivated by default.
 @end table
 
+@item -Warray-parameter
+@itemx -Warray-parameter=@var{n}
+@opindex Wno-array-parameter
+Warn about redeclarations of functions involving arguments of array or
+pointer types of inconsistent kinds or forms, and enable the detection
+of out-of-bounds accesses to such parameters by warnings such as
+@option{-Warray-bounds}.
+
+If the first function declaration uses the array form the bound specified
+in the array is assumed to be the minimum number of elements expected to
+be provided in calls to the function and the maximum number of elements
+accessed by it.  Failing to provide arguments of sufficient size or accessing
+more than the maximum number of elements may be diagnosed by warnings such
+as @option{-Warray-bounds}.  At level 1 the warning diagnoses inconsistencies
+involving array parameters declared using the @code{T[static N]} form.
+
+For example, the warning triggers for the following redeclarations because
+the first one allows an array of any size to be passed to @code{f} while
+the second one with the keyword @code{static} specifies that the array
+argument must have at least four elements.
+
+@smallexample
+void f (int[static 4]);
+void f (int[]);           // warning (inconsistent array form)
+
+void g (void)
+@{
+  int *p = (int *)malloc (4);
+  f (p);                  // warning (array too small)
+  @dots{}
+@}
+@end smallexample
+
+At level 2 the warning also triggers for redeclarations involving any other
+inconsistency in array or pointer argument forms denoting array sizes.
+Pointers and arrays of unspecified bound are considered equivalent and do
+not trigger a warning.
+
+@smallexample
+void g (int*);
+void g (int[]);     // no warning
+void g (int[8]);    // warning (inconsistent array bound)
+@end smallexample
+
+@option{-Warray-parameter=2} is included in @option{-Wall}.  The
+@option{-Wvla-parameter} option triggers warnings for similar inconsistencies
+involving Variable Length Array arguments.
+
 @item -Wattribute-alias=@var{n}
 @itemx -Wno-attribute-alias
 @opindex Wattribute-alias
@@ -8531,6 +8581,44 @@ See also @option{-Walloca-larger-than=@var{byte-size}}.
 Disable @option{-Wvla-larger-than=} warnings.  The option is equivalent
 to @option{-Wvla-larger-than=}@samp{SIZE_MAX} or larger.
 
+@item -Wvla-parameter
+@opindex Wno-vla-parameter
+Warn about redeclarations of functions involving arguments of Variable
+Length Array types of inconsistent kinds or forms, and enable the detection
+of out-of-bounds accesses to such parameters by warnings such as
+@option{-Warray-bounds}.
+
+If the first function declaration uses the VLA form the bound specified
+in the array is assumed to be the minimum number of elements expected to
+be provided in calls to the function and the maximum number of elements
+accessed by it.  Failing to provide arguments of sufficient size or
+accessing more than the maximum number of elements may be diagnosed.
+
+For example, the warning triggers for the following redeclarations because
+the first one allows an array of any size to be passed to @code{f} while
+the second one specifies that the array argument must have at least @code{n}
+elements.  In addition, calling @code{f} with the assotiated VLA bound
+parameter in excess of the actual VLA bound triggers a warning as well.
+
+@smallexample
+void f (int n, int[n]);
+void f (int, int[]);     // warning: argument 2 previously declared as a VLA
+
+void g (int n)
+@{
+    if (n > 4)
+      return;
+    int a[n];
+    f (sizeof a, a);     // warning: access to a by f may be out of bounds
+  @dots{}
+@}
+
+@end smallexample
+
+@option{-Wvla-parameter} is included in @option{-Wall}.  The
+@option{-Warray-parameter} option triggers warnings for similar problems
+involving ordinary array arguments.
+
 @item -Wvolatile-register-var
 @opindex Wvolatile-register-var
 @opindex Wno-volatile-register-var
diff --git a/gcc/testsuite/c-c++-common/Warray-bounds-6.c b/gcc/testsuite/c-c++-common/Warray-bounds-6.c
index 6611d5cd916..5e787360314 100644
--- a/gcc/testsuite/c-c++-common/Warray-bounds-6.c
+++ b/gcc/testsuite/c-c++-common/Warray-bounds-6.c
@@ -1,8 +1,12 @@
-/* PR tree-optimization/86614 */
-/* { dg-do compile } */
-/* { dg-options "-O2 -Warray-bounds" } */
+/* PR tree-optimization/86614 - duplicate -Warray-bounds for a strncpy
+   call with out-of-bounds offset
+   { dg-do compile }
+   { dg-options "-O2 -Warray-bounds" } */
 
-extern char *strncpy (char *, const char *, __SIZE_TYPE__);
+#if __cplusplus
+extern "C"
+#endif
+char *strncpy (char *, const char *, __SIZE_TYPE__);
 
 void sink (void *);
 
@@ -12,7 +16,8 @@ void g (const char *s, unsigned n)
 {
   int i = (char *)a[1].b - (char *)a + 1;
   char *d = a[1].b;
-  /* Ensure the same bug is not diagnosed more than once.  */
-  strncpy (d + i, s, n);	/* { dg-warning "array subscript \[0-9]+ is outside array bounds of" } */
-				/* { dg-bogus "offset \[0-9]+ is out of the bounds \\\[0, \[0-9]+\\\] of object 'a' with type" "" { target *-*-* } .-1 } */
+  /* Verify the bug is diagnosed exactly once, using either form
+     of the warning.  */
+  strncpy (d + i, s, n);	/* { dg-warning "array subscript \[0-9]+ is outside array bounds|offset \[0-9]+ is out of the bounds" } */
+				/* { dg-bogus "offset \[0-9]+ is out of the bounds|array subscript \[0-9]+ is outside array bounds" "" { target *-*-* } .-1 } */
 }
diff --git a/gcc/testsuite/gcc.dg/Warray-parameter-2.c b/gcc/testsuite/gcc.dg/Warray-parameter-2.c
new file mode 100644
index 00000000000..88f20e203ce
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Warray-parameter-2.c
@@ -0,0 +1,45 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+   { dg-do compile }
+   { dg-options "-Wall" } */
+
+// Reduced from Glibc.
+
+typedef struct FILE FILE;
+
+int vfprintf (FILE*, const char*, __builtin_va_list);
+int vfprintf (FILE*, const char*, __builtin_va_list);   // { dg-bogus "-Warray-parameter" }
+int vfprintf (FILE*, const char*, __builtin_va_list);
+
+int vfscanf (FILE*, const char*, __builtin_va_list);
+int vfscanf (FILE*, const char*, __builtin_va_list);    // { dg-bogus "-Warray-parameter" }
+int vfscanf (FILE*, const char*, __builtin_va_list);
+
+
+/* Verify that mismatches in array/to pointer to va_list are still
+   diagnosed.  */
+
+int fva (__builtin_va_list);
+int fva (__builtin_va_list);
+
+int fpva_a1 (__builtin_va_list*);
+int fpva_a1 (__builtin_va_list[1]);     // { dg-warning "\\\[-Warray-parameter" }
+
+int fpva_a_ (__builtin_va_list*);
+int fpva_a_ (__builtin_va_list[]);
+int fpva_a_ (__builtin_va_list*);
+int fpva_a_ (__builtin_va_list[]);
+
+/* Also verify that a mismatch between a pointer and a one-element
+   array are diagnosed.  This is pervasive in Glibc headers but
+   making an exception for it would leave no way to express
+   the requirement that a function take at least one argument
+   by reference.  */
+
+struct __jmp_buf_tag;
+int __sigsetjmp (struct __jmp_buf_tag*, int);
+
+struct __jmp_buf_tag { };
+typedef struct __jmp_buf_tag jmp_buf[1];
+
+int __sigsetjmp (struct __jmp_buf_tag[1], int);   // { dg-warning "\\\[-Warray-parameter" }
diff --git a/gcc/testsuite/gcc.dg/Warray-parameter-3.c b/gcc/testsuite/gcc.dg/Warray-parameter-3.c
new file mode 100644
index 00000000000..cbf3e9339f5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Warray-parameter-3.c
@@ -0,0 +1,89 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+   { dg-do compile }
+   { dg-options "-Wall -Warray-parameter=1" } */
+
+/* Verify that at level 1 mismatches in the bounds of ordinary array
+   parameters don't trigger -Warray-parameter.  */
+void fax (int[]);
+void fax (int[0]);
+void fax (int[1]);
+void fax (int[2]);
+void fax (int[3]);
+
+/* Same as above but starting with an array with a specified bound.  */
+void gax (int[3]);
+void gax (int[2]);
+void gax (int[1]);
+void gax (int[0]);
+void gax (int[]);
+
+/* Same for multidimensional arrays.  */
+void fax_y (int[][3]);
+void fax_y (int[0][3]);
+void fax_y (int[1][3]);
+void fax_y (int[2][3]);
+void fax_y (int[3][3]);
+
+/* Same as above but starting with an array with a specified bound.  */
+void gax_y (int[3][5]);
+void gax_y (int[2][5]);
+void gax_y (int[1][5]);
+void gax_y (int[0][5]);
+void gax_y (int[][5]);
+
+/* Exercise VLAs with a mismatch in the bound for an ordinary array.  */
+void fvlax_y (int n, int[][n]);
+void fvlax_y (int n, int[0][n]);
+void fvlax_y (int n, int[1][n]);
+void fvlax_y (int n, int[2][n]);
+void fvlax_y (int n, int[3][n]);
+
+void fvlaxn_y (int n, int[][n]);
+void fvlaxn_y (int n, int[0][n]);
+void fvlaxn_y (int n, int[1][n]);
+void fvlaxn_y (int n, int[2][n]);
+void fvlaxn_y (int n, int[3][n]);
+
+void fvlaxx_y (int[][*]);
+void fvlaxx_y (int[0][*]);
+void fvlaxx_y (int[1][*]);
+void fvlaxx_y (int[2][*]);
+void fvlaxx_y (int[3][*]);
+
+/* Verify that mismatches in the bounds of array parameters declared
+   static do trigger -Warray-parameter.  */
+void fas1 (int[static 1]);    // { dg-message "previously declared as 'int\\\[static 1]'" }
+void fas1 (int[static 2]);    // { dg-warning "\\\[-Warray-parameter=" }
+
+
+/* Also verify that -Warray-bounds doesn't trigger for ordinary array
+   parameters...  */
+#pragma GCC optimize "2"
+
+__attribute__ ((noipa)) void
+gca3 (char a[3])
+{
+  a[0] = 0; a[1] = 1; a[2] = 2; a[3] = 3;
+}
+
+__attribute__ ((noipa)) void
+gia3 (int a[3])
+{
+  a[0] = 0; a[1] = 1; a[2] = 2; a[3] = 3;
+}
+
+/* ...but does for static arrays.  */
+__attribute__ ((noipa)) void
+gcas3 (char a[static 3])
+{
+  a[0] = 0; a[1] = 1; a[2] = 2;
+  a[3] = 3;                   // { dg-warning "\\\[-Warray-bounds" }
+}
+
+__attribute__ ((noipa)) void
+gias3 (int a[static 3])
+{
+  a[0] = 0; a[1] = 1; a[2] = 2;
+  a[3] = 3;                   // { dg-warning "\\\[-Warray-bounds" }
+}
diff --git a/gcc/testsuite/gcc.dg/Warray-parameter-4.c b/gcc/testsuite/gcc.dg/Warray-parameter-4.c
new file mode 100644
index 00000000000..b702d730a13
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Warray-parameter-4.c
@@ -0,0 +1,119 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+   Verify warnings for multidimensional arrays, including mismatches
+   in bounds of arrays of VLAs.  (Mismatches in variable bounds are
+   diagnosed by -Wvla-parameter.)
+   { dg-do compile }
+   { dg-options "-Wall -Warray-parameter=2" } */
+
+// Verify that equivalent forms don't tigger a warning.
+
+typedef int IA1[1];
+typedef int IA1x_[][1];
+typedef int IA1x0[0][1];
+
+void fia_x1 (int[][1]);
+void fia_x1 (IA1[0]);
+void fia_x1 (IA1x_);
+void fia_x1 (IA1x0);
+void fia_x1 (int[0][1]);
+void fia_x1 (int[static 0][1]);
+
+// Same as above one more time.
+void fia_x1 (int[][1]);
+void fia_x1 (IA1[0]);
+void fia_x1 (IA1x_);
+void fia_x1 (IA1x0);
+void fia_x1 (int[0][1]);
+void fia_x1 (int[static 0][1]);
+
+
+void fia1x1 (int[1][1]);
+void fia1x1 (int[1][1]);
+void fia1x1 (int[static 1][1]);
+
+void fia2x1 (int[2][1]);
+void fia2x1 (int[2][1]);
+void fia2x1 (int[static 2][1]);
+
+void fia1x2 (int[1][2]);
+void fia1x2 (int[1][2]);
+void fia1x2 (int[static 1][2]);
+
+void fia1x1_2x1 (int[1][1]);          // { dg-message "previously declared as 'int\\\[1]\\\[1]'" }
+void fia1x1_2x1 (int[2][1]);          // { dg-warning "\\\[-Warray-parameter" }
+void fia1x1_2x1 (int[static 1][1]);
+void fia1x1_2x1 (int[static 2][1]);   // { dg-warning "\\\[-Warray-parameter" }
+
+
+void fia2x1_1x1 (int[2][1]);          // { dg-message "previously declared as 'int\\\[2]\\\[1]'" }
+void fia2x1_1x1 (int[1][1]);          // { dg-warning "\\\[-Warray-parameter" }
+void fia2x1_1x1 (int[2][1]);
+void fia2x1_1x1 (int[static 1][1]);   // { dg-warning "\\\[-Warray-parameter" }
+void fia2x1_1x1 (int[static 2][1]);
+
+
+extern int n1, n2;
+
+void fca_xn1 (char[][n1]);
+void fca_xn1 (char[0][n1]);
+void fca_xn1 (char[static 0][n1]);
+
+void fca1xn1_2xn1 (char[1][n1]);
+void fca1xn1_2xn1 (char[2][n1]);        // { dg-warning "\\\[-Warray-parameter" }
+void fca1xn1_2xn1 (char[1][n1]);
+void fca1xn1_2xn1 (char[static 1][n1]);
+void fca1xn1_2xn1 (char[static 2][n1]); // { dg-warning "\\\[-Warray-parameter" }
+
+
+/* Exercise VLAs with a mismatch in the bound for an ordinary array.  */
+void fvlax_y (int n, int[][n]);
+void fvlax_y (int n, int[0][n]);
+void fvlax_y (int n, int[1][n]);        // { dg-warning "argument 2 of type 'int\\\[1]\\\[n]' with mismatched bound" }
+void fvlax_y (int n, int[2][n]);        // { dg-warning "argument 2 of type 'int\\\[2]\\\[n]' with mismatched bound" }
+void fvlax_y (int n, int[3][n]);        // { dg-warning "argument 2 of type 'int\\\[3]\\\[n]' with mismatched bound" }
+
+void fvlaxn_y (int n, int[][n]);
+void fvlaxn_y (int n, int[0][n]);
+void fvlaxn_y (int n, int[1][n]);       // { dg-warning "\\\[-Warray-parameter" }
+void fvlaxn_y (int n, int[2][n]);       // { dg-warning "\\\[-Warray-parameter" }
+void fvlaxn_y (int n, int[3][n]);       // { dg-warning "\\\[-Warray-parameter" }
+
+void fvlaxx_y (int[][*]);
+void fvlaxx_y (int[0][*]);
+void fvlaxx_y (int[1][*]);              // { dg-warning "\\\[-Warray-parameter" }
+void fvlaxx_y (int[2][*]);              // { dg-warning "\\\[-Warray-parameter" }
+void fvlaxx_y (int[3][*]);              // { dg-warning "\\\[-Warray-parameter" }
+
+
+// Verify an array of pointers to an array of function pointers.
+
+void ffpa7_5 (void (* (* (* [7])[5])(void))(void));
+// { dg-message "previously declared as 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[7]\\\)\\\[5]\\\)\\\(void\\\)\\\)\\\(void\\\)'" "note" { target *-*-* } .-1 }
+void ffpa7_5 (void (* (* (* [6])[5])(void))(void));
+// { dg-warning "argument 1 of type 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[6]\\\)\\\[5]\\\)\\\(void\\\)\\\)\\\(void\\\)' with mismatched bound" "" { target *-*-* } .-1 }
+void ffpa7_5 (void (* (* (* [])[5])(void))(void));
+// { dg-warning "argument 1 of type 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[]\\\)\\\[5]\\\)\\\(void\\\)\\\)\\\(void\\\)' with mismatched bound" "" { target *-*-* } .-1 }
+void ffpa7_5 (void (* (* (* (*))[5])(void))(void));
+// { dg-warning "argument 1 of type 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\*\\\)\\\[5]\\\)\\\(void\\\)\\\)\\\(void\\\)' declared as a pointer" "" { target *-*-* } .-1 }
+
+// Same as above but with array of pointers to a VLA of function pointers.
+void ffpa7_n1 (void (* (* (* [7])[n1])(void))(void));
+// { dg-message "previously declared as 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[7]\\\)\\\[n1]\\\)\\\(void\\\)\\\)\\\(void\\\)'" "note" { target *-*-* } .-1 }
+void ffpa7_n1 (void (* (* (* [8])[n1])(void))(void));
+// { dg-warning "argument 1 of type 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[8]\\\)\\\[n1]\\\)\\\(void\\\)\\\)\\\(void\\\)' with mismatched bound" "" { target *-*-* } .-1 }
+
+void ffpa9_x (void (* (* (* [9])[*])(void))(void));
+// { dg-message "previously declared as 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[9]\\\)\\\[\\\*]\\\)\\\(void\\\)\\\)\\\(void\\\)'" "pr?????" { xfail *-*-* } .-1 }
+// { dg-message "previously declared as 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[9]\\\)\\\[0]\\\)\\\(void\\\)\\\)\\\(void\\\)'" "" { target *-*-* } .-2 }
+void ffpa9_x (void (* (* (* [8])[*])(void))(void));
+// { dg-warning "argument 1 of type 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[8]\\\)\\\[\\\*]\\\)\\\(void\\\)\\\)\\\(void\\\)' with mismatched bound" "pr?????" { xfail *-*-* } .-1 }
+// { dg-warning "argument 1 of type 'void \\\(\\\* ?\\\(\\\* ?\\\(\\\*\\\[8]\\\)\\\[0]\\\)\\\(void\\\)\\\)\\\(void\\\)' with mismatched bound"  "" { target *-*-* } .-2 }
+
+/* Verify a three-dimensional array of pointers to two-dimensional arrays
+   of pointers to function pointers.  */
+
+void ffpa7_5_3 (void (* (* (* (* [7])[5])[3])(void))(void));
+// { dg-message "previously declared as 'void ?\\\(\\\* ?\\\(\\\* ?\\\(\\\* ?\\\(\\\* ?\\\[7]\\\)\\\[5]\\\)\\\[3]\\\)\\\(void\\\)\\\)\\\(void\\\)'" "note" { target *-*-* } .-1 }
+void ffpa7_5_3 (void (* (* (* (* [1])[5])[3])(void))(void));
+// { dg-warning "argument 1 of type 'void ?\\\(\\\* ?\\\(\\\* ?\\\(\\\* ?\\\(\\\* ?\\\[1]\\\)\\\[5]\\\)\\\[3]\\\)\\\(void\\\)\\\)\\\(void\\\)' with mismatched bound" "" { target *-*-* } .-1 }
diff --git a/gcc/testsuite/gcc.dg/Warray-parameter-5.c b/gcc/testsuite/gcc.dg/Warray-parameter-5.c
new file mode 100644
index 00000000000..6e89bf0c801
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Warray-parameter-5.c
@@ -0,0 +1,14 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+   Verify that -Warray-parameter diagnoses mismatches in bounds of
+   arrays between redeclarations of the same function and with pointer
+   parameters pointing to those arrays.
+   { dg-do compile }
+   { dg-options "-Wall -Warray-parameter" } */
+
+void fa_x (int (*)[]);        // { dg-message "previously declared as 'int \\\(\\\*\\\)\\\[]'" }
+void fa_x (int (*)[2]);       // { dg-warning "\\\[-Warray-parameter" }
+void fa_x (int (*)[2]);       // { dg-warning "mismatch in bound 1 of argument 1 declared as 'int \\\(\\\*\\\)\\\[2]'" }
+
+void fa_2 (int (*)[2]);       // { dg-message "previously declared as 'int \\\(\\\*\\\)\\\[2]'" }
+void fa_2 (int (*)[]);        // { dg-warning "mismatch in bound 1 of argument 1 declared as 'int \\\(\\\*\\\)\\\[]'" }
diff --git a/gcc/testsuite/gcc.dg/Warray-parameter.c b/gcc/testsuite/gcc.dg/Warray-parameter.c
new file mode 100644
index 00000000000..42be3100e45
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Warray-parameter.c
@@ -0,0 +1,187 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+   Verify that -Warray-parameter diagnoses mismatches in array (and
+   pointer) arrguments between redeclarations of the same function.
+   Also verify that the array/pointer argument form in a mismatched
+   redeclaration doesn't override the form in the initial declaration.
+   { dg-do compile }
+   { dg-options "-Wall -Warray-parameter -Wno-vla-paramater" } */
+
+/* Redclarations with the same or equivalent array form should not
+   be dianosed.  T[0] is diagnosed by -Wpedantic for being invalid
+   C so there's little point in also warning for the difference in
+   array form.  */
+void f1vpp (void**);
+void f1vpp (void*[]);
+void f1vpp (void*[0]);
+
+void f1ia_ (int[]);
+void f1ia_ (int[]);
+void f1ia_ (int[0]);
+/* Verify the unused attribute still has an effect.  */
+void f1ia_ (int a[0] __attribute__ ((unused))) { }
+void f1ia_ (int[]);
+
+void f1ia_p (int[]);
+void f1ia_p (int*);
+void f1ia_p (int *p  __attribute__ ((unused))) { }
+void f1ia_p (int[]);
+
+void f1p_ia (const int*);
+void f1p_ia (const int[]);
+void f1p_ia (const int *p __attribute__ ((unused))) { }
+void f1p_ia (const int[]);
+
+void f1ia1 (int[1]);
+void f1ia1 (int[1]);
+void f1ia1 (int[2 - 1]);
+
+void f1ias2 (int[static 2]);
+void f1ias2 (int[static 2]);
+void f1ias2 (int[static 1 + 1]);
+void f1ias2 (int a[static 3 - 1]) { (void)&a; }
+
+void f1ipa_ (int*[]);
+void f1ipa_ (int*[]);
+void f1ipa_ (int*[0]);
+
+void f1ia1_x (int[1]);          // { dg-message "previously declared as 'int\\\[1]'" }
+void f1ia1_x (int[]);           // { dg-warning "argument 1 of type 'int\\\[]' with mismatched bound" }
+void f1ia1_x (int[]);           // { dg-warning "argument 1 of type 'int\\\[]' with mismatched bound" }
+void f1ia1_x (int[1]);
+void f1ia1_x (int[2]);          // { dg-warning "argument 1 of type 'int\\\[2]' with mismatched bound" }
+void f1ia1_x (int[1]);
+void f1ia1_x (int[3]);          // { dg-warning "argument 1 of type 'int\\\[3]' with mismatched bound" }
+void f1ia1_x (int a[1] __attribute__ ((unused))) { }
+
+
+void f1ias2_s3 (int[static 2]); // { dg-message "previously declared as 'int\\\[static 2]'" }
+void f1ias2_s3 (int[static 3]); // { dg-warning "argument 1 of type 'int\\\[static 3]' with mismatched bound" }
+/* Verify the unused attribute still has an effect and doesn't interfere
+   with the warning.  */
+void f1ias2_s3 (int a[static 3] __attribute__ ((unused))) { }  // { dg-warning "argument 1 of type 'int\\\[static 3]' with mismatched bound" }
+
+
+/* Ordinary T[N] and T[static N] forms are both effectively treated
+   the same but strictly have different meanings so they are diagnosed.
+   It might be worth splitting the warning into two levels and having
+   only the higher level treat the ordinary form as T[static N].  */
+
+void f1ia3_s4 (int[3]);         // { dg-message "previously declared as 'int\\\[3]'" }
+void f1ia3_s4 (int[static 4]);  // { dg-warning "argument 1 of type 'int\\\[static 4]' with mismatched bound" }
+void f1ia3_s4 (int[3]);
+
+
+void f1ias4_5 (int[static 4]);  // { dg-message "previously declared as 'int\\\[static 4]'" }
+void f1ias4_5 (int[5]);         // { dg-warning "argument 1 of type 'int\\\[5]' with mismatched bound" }
+void f1ias4_5 (int[static 4]);
+
+
+void f1ia_1 (int[]);            // { dg-message "previously declared as 'int\\\[]'" }
+void f1ia_1 (int[1]);           // { dg-warning "argument 1 of type 'int\\\[1]' with mismatched bound" }
+void f1ia_1 (int[]);
+
+
+void f1ca_ (char[]);            // { dg-message "previously declared as 'char\\\[]'" }
+void f1ca_ (char[2]);           // { dg-warning "argument 1 of type 'char\\\[2]' with mismatched bound" }
+void f1ca_ (char[]);
+
+
+void f1csp (const short*);      // { dg-message "previously declared as 'const short int ?\\\*'" }
+void f1csp (const short[3]);    // { dg-warning "argument 1 of type 'const short int\\\[3]' with mismatched bound" }
+void f1csp (const short*);
+
+
+void f1ia2 (int[2]);            // { dg-message "previously declared as 'int\\\[2]'" }
+void f1ia2 (int[1]);            // { dg-warning "argument 1 of type 'int\\\[1]' with mismatched bound" }
+void f1ia2 (int[2]);
+
+
+void f1cvla2 (const volatile long[3]);  // { dg-message "previously declared as 'const volatile long int\\\[3]'" }
+void f1cvla2 (const volatile long[2]);  // { dg-warning "argument 1 of type 'const volatile long int\\\[2]' with mismatched bound" }
+void f1cvla2 (const volatile long[3]);
+void f1cvla2 (const volatile long[restrict 4]); // { dg-warning "argument 1 of type 'const volatile long int\\\[restrict 4]' with mismatched bound" }
+
+
+void f1afa4 (_Atomic float[3]);         // { dg-message "previously declared as an array '_Atomic float ?\\\[3]'" }
+void f1afa4 (_Atomic float*);           // { dg-warning "argument 1 of type '_Atomic float ?\\\*' declared as a pointer" }
+void f1afa4 (_Atomic float[3]);
+
+void f1ipa1_a2 (int*[1]);       // { dg-message "previously declared as 'int \\\*\\\[1]'" }
+void f1ipa1_a2 (int*[2]);       // { dg-warning "argument 1 of type 'int \\\*\\\[2]' with mismatched bound" }
+void f1ipa1_a2 (int*[1]);
+
+
+typedef int IAx[];
+typedef int IA1[1];
+typedef int IA2[2];
+typedef int IA3[3];
+
+// The message should differentiate between the [] form and *.
+void f1IAx_A1 (IAx);            // { dg-message "previously declared as 'int\\\[]'" "pr?????" { xfail *-*-* } }
+                                // { dg-message "previously declared as 'int *\\\*'" "note" { target *-*-* } .-1 }
+void f1IAx_A1 (IA1);            // { dg-message "argument 1 of type 'int\\\[1]' with mismatched bound" }
+
+void f1IA1_A2 (IA1);            // { dg-message "previously declared as 'int\\\[1]'" }
+void f1IA1_A2 (IA2);            // { dg-warning "argument 1 of type 'int\\\[2]' with mismatched bound" }
+void f1IA1_A2 (IA1);
+void f1IA1_A2 (int[2]);         // { dg-warning "argument 1 of type 'int\\\[2]' with mismatched bound" }
+
+
+void f1IA1_A3 (IA1 ia1);        // { dg-message "previously declared as 'int\\\[1]'" }
+void f1IA1_A3 (IA3 ia3);        // { dg-warning "argument 1 of type 'int\\\[3]' with mismatched bound" }
+void f1IA1_A3 (IA1 ia1);
+
+
+void f1IA2_A3 (IA2 a);          // { dg-message "previously declared as 'int\\\[2]'" }
+void f1IA2_A3 (IA3 a);          // { dg-warning "argument 1 of type 'int\\\[3]' with mismatched bound" }
+void f1IA2_A3 (IA2 a);
+
+
+// Verify multiple array arguments.
+
+void f2a2_a3_3_3 (int[2], int[3]);  // { dg-message "previously declared as 'int\\\[2]'" }
+void f2a2_a3_3_3 (int[2], int[3]);
+void f2a2_a3_3_3 (int[3], int[3]);  // { dg-warning "argument 1 of type 'int\\\[3]' with mismatched bound" }
+
+
+void f2a2_a3_2_4 (int[2], int[3]);  // { dg-message "previously declared as 'int\\\[3]'" }
+void f2a2_a3_2_4 (int[2], int[4]);  // { dg-warning "argument 2 of type 'int\\\[4]' with mismatched bound" }
+
+
+/* Verify that pointers to arrays and arrays of arrays are differentiated
+   the same way as pointers and arrays of other types.  */
+typedef IA1 *PA1;
+
+void fpia1 (IA1*);              // { dg-message "previously declared as 'int ?\\(\\\*\\)\\\[1]'" }
+void fpia1 (IA1[1]);            // { dg-warning "argument 1 of type 'int\\\[1]\\\[1]' with mismatched bound" }
+void fpia1 (PA1);
+void fpia1 (int(*)[1]);
+void fpia1 (int[][1]);
+
+void f1vpa1 (void*[][1]);
+void f1vpa1 (void*[0][1]);
+
+/* Verify arrays of pointers.  */
+void vaip1 (int (*[3]));       // { dg-message "previously declared as 'int *\\\*\\\[3]'" }
+void vaip1 (int (*[5]));       // { dg-warning "argument 1 of type 'int *\\\*\\\[5]' with mismatched bound" }
+void vaip1 (int (*[3]));
+void vaip1 (int (*[]));        // { dg-warning "argument 1 of type 'int *\\\*\\\[]' with mismatched bound" }
+void vaip1 (int (*[3]));
+
+/* Verify that attributes with arrays don't cause unwanted warnings and
+   don't suppress intended ones.  */
+
+#define ALIGN(N)__attribute__ ((aligned (__alignof__ (char[N]))))
+
+void fatipa2 (int (* ALIGN (3)[2]));  // { dg-message "previously declared as 'int \\\*\\\[2]'" }
+void fatipa2 (int (* ALIGN (4)[2]));
+void fatipa2 (int (* ALIGN (5)[2]));
+void fatipa2 (int (* ALIGN (7)[3]));  // { dg-warning "argument 1 of type 'int \\\*\\\[3]' with mismatched bound" }
+
+void fatiap (int (* ALIGN (3))[2]);
+void fatiap (int (* ALIGN (5))[2]);
+
+
+void fatipa3 (int (* ALIGN (1) (* ALIGN (2))[3]));
+void fatipa3 (int (* ALIGN (1) (* ALIGN (2))[3]));
diff --git a/gcc/testsuite/gcc.dg/Wbuiltin-declaration-mismatch-9.c b/gcc/testsuite/gcc.dg/Wbuiltin-declaration-mismatch-9.c
index f0c1ce33267..da767b87700 100644
--- a/gcc/testsuite/gcc.dg/Wbuiltin-declaration-mismatch-9.c
+++ b/gcc/testsuite/gcc.dg/Wbuiltin-declaration-mismatch-9.c
@@ -10,3 +10,6 @@ void a (void)
 	  ""        /* { dg-warning "passing argument 2 of .sscanf. from incompatible pointer type" } */
 	  );
 }
+
+/* The scanf call may also trigger:
+   { dg-prune-output "-Wstringop-overflow" } */
diff --git a/gcc/testsuite/gcc.dg/Wvla-parameter-2.c b/gcc/testsuite/gcc.dg/Wvla-parameter-2.c
new file mode 100644
index 00000000000..ba9324143b4
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wvla-parameter-2.c
@@ -0,0 +1,75 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+   Verify the -Wvla-parameter warnings correctly diagnose mismatches
+   between multimensional array arguments with one or more variable
+   bounds in redeclarations of the same function.
+   { dg-do compile }
+   { dg-options "-Wall -Wvla-parameter" } */
+
+void fmn_a1n_axn (int n, int[1][n]);            // { dg-message "previously declared as 'int\\\[1]\\\[n]' with 1 variable bound" "note" }
+void fmn_a1n_axn (int n, int[*][n]);            // { dg-warning "argument 2 of type 'int\\\[\\\*]\\\[n]' declared with 2 variable bounds" }
+
+
+void fmn_axn_a2n (int n, int[*][n]);            // { dg-message "previously declared as 'int\\\[\\\*]\\\[n]' with 2 variable bounds" "note" }
+void fmn_axn_a2n (int n, int[2][n]);            // { dg-warning "argument 2 of type 'int\\\[2]\\\[n]' declared with 1 variable bound" }
+
+
+void fmn_amn_axn (int m, int n, int[m][n]);     // { dg-message "previously declared as 'int\\\[m]\\\[n]' with 0 unspecified variable bounds" "note" }
+void fmn_amn_axn (int m, int n, int[*][n]);     // { dg-warning "argument 3 of type 'int\\\[\\\*]\\\[n]' declared with 1 unspecified variable bound" }
+
+// Same as above but a different function name.
+void gmn_amn_axn (int m, int n, int[m][n]);     // { dg-message "previously declared as 'int\\\[m]\\\[n]' with 0 unspecified variable bounds" "note" }
+void gmn_amn_axn (int m, int n, int[*][n]);     // { dg-warning "argument 3 of type 'int\\\[\\\*]\\\[n]' declared with 1 unspecified variable bound" }
+
+typedef int A7[7];
+
+void fm_A7_m_5 (int m, A7[m][5]);               // { dg-message "previously declared as 'int\\\[m]\\\[5]\\\[7]' with bound argument 1" "note" }
+void fm_A7_m_5 (int n, A7[n][5]);
+
+void fm_A7_m_5 (int n, A7[n + 1][5]);           // { dg-warning "argument 2 of type 'int\\\[n \\\+ 1]\\\[5]\\\[7]' declared with mismatched bound 'n \\\+ 1'" }
+
+
+int n1, n2, n3, n4, n5, n6, n7, n8, n9;
+void f (int[n1][2][n3][4][n5][6][n7][8][n9]);   // { dg-message "previously declared as 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[n5]\\\[6]\\\[n7]\\\[8]\\\[n9]' with 0 unspecified variable bounds" "note" }
+                                                // { dg-message "with 5 variable bounds" "note" { target *-*-* } .-1  }
+void f (int[n1][2][n3][4][n5][6][n7][8][n9]);
+
+/* Due to a limitation and because [*] is represented the same as [0]
+   only the most significant array bound is rendered as [*]; the others
+   are rendered as [0].  */
+void f (int[n1][2][n3][4][n5][6][n7][8][*]);    // { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[n5]\\\[6]\\\[n7]\\\[8]\\\[\\\*]' declared with 1 unspecified variable bound" "pr?????" { xfail *-*-* } }
+// { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[n5]\\\[6]\\\[n7]\\\[8]\\\[0]' declared with 1 unspecified variable bound" "pr?????" { target *-*-* } .-1 }
+void f (int[n1][2][n3][4][n5][6][*][8][n9]);   // { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[n5]\\\[6]\\\[\\\*]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" "pr?????" { xfail *-*-* } }
+// { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[n5]\\\[6]\\\[0]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" "pr?????" { target *-*-* } .-1 }
+void f (int[n1][2][n3][4][*][6][n7][8][n9]);   // { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[\\\*]\\\[6]\\\[n7]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" "pr?????" { xfail *-*-*} }
+// { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[n3]\\\[4]\\\[0]\\\[6]\\\[n7]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" "pr?????" { target *-*-* } .-1 }
+void f (int[n1][2][*][4][n5][6][n7][8][n9]);   // { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[\\\*]\\\[4]\\\[n5]\\\[6]\\\[n7]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" "pr?????" { xfail *-*-* } }
+// { dg-warning "argument 1 of type 'int\\\[n1]\\\[2]\\\[0]\\\[4]\\\[n5]\\\[6]\\\[n7]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" "pr?????" { target *-*-* } .-1 }
+void f (int[*][2][n3][4][n5][6][n7][8][n9]);   // { dg-warning "argument 1 of type 'int\\\[\\\*]\\\[2]\\\[n3]\\\[4]\\\[n5]\\\[6]\\\[n7]\\\[8]\\\[n9]' declared with 1 unspecified variable bound" }
+
+void f (int[n1][n2][n3][n4][n5][n6][n7][n8][n9]);   // { dg-warning "argument 1 of type 'int\\\[n1]\\\[n2]\\\[n3]\\\[n4]\\\[n5]\\\[n6]\\\[n7]\\\[n8]\\\[n9]' declared with 9 variable bounds" }
+
+// Verify that arrays of pointers to arrays...etc are handled correctly.
+void a2pampan (int (*(*(*[2])[n1])[n2]));
+// { dg-message "previously declared as 'int \\\* \\\(\\\* \\\(\\\*\\\[2]\\\)\\\[n1]\\\)\\\[n2]'" "note" { target *-*-* } .-1 }
+void a2pampan (int (*(*(*[2])[n1])[1]));
+// { dg-warning "argument 1 of type 'int \\\* \\\(\\\* \\\(\\\*\\\[2]\\\)\\\[n1]\\\)\\\[1]' declared with 1 variable bound" "" { target *-*-* } .-1  }
+void a2pampan (int (*(*(*[2])[1])[n2]));
+// { dg-warning "argument 1 of type 'int \\\* \\\(\\\* \\\(\\\*\\\[2]\\\)\\\[1]\\\)\\\[n2]' declared with 1 variable bound" "" { target *-*-* } .-1  }
+void a2pampan (int (*(*(*[2])[n1])[n1]));
+// { dg-warning "argument 1 of type 'int \\\* \\\(\\\* \\\(\\\*\\\[2]\\\)\\\[n1]\\\)\\\[n1]' declared with mismatched bound 'n1'" "" { target *-*-* } .-1  }
+void a2pampan (int (*(*(*[2])[n1])[n2]));
+
+
+/* Verify that the presence or absence of static with VLA dooesn't cause
+   unwanted warnings.  */
+
+int f2ia1_1 (int n, int [n][n]);            // { sg-message "previously declared as 'int\\\[n]\\\[n]' with bound argument 1" }
+int f2ia1_1 (int n, int[static n][n]);
+int f2ia1_1 (int n, int a[static n][n]) { return sizeof *a; }
+int f2ia1_1 (int n, int[static n + 1][n]);  // { dg-warning "argument 2 of type 'int\\\[n \\\+ 1]\\\[n]' declared with mismatched bound 'n \\\+ 1'" }
+
+int f2ias1_1 (int n, int [static n][n]);    // { dg-message "previously declared as 'int\\\[n]\\\[n]' with bound argument 1" }
+int f2ias1_1 (int n, int[n][n]);
+int f2ias1_1 (int n, int a[++n][n])         // { dg-warning "argument 2 of type 'int\\\[\\\+\\\+n]\\\[n]' declared with mismatched bound ' ?\\+\\+n'" }
+{ return sizeof *a; }
diff --git a/gcc/testsuite/gcc.dg/Wvla-parameter-3.c b/gcc/testsuite/gcc.dg/Wvla-parameter-3.c
new file mode 100644
index 00000000000..51f01729b1d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wvla-parameter-3.c
@@ -0,0 +1,68 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+   Verify that redeclarations of functions with pointer parameters to
+   arrays with variable bounds are diagnosed if the bounds don't match
+   either in kind or in the variable expression.
+   { dg-do compile }
+   { dg-options "-Wall -Wvla-parameter" } */
+
+extern int m, n;
+
+void pa_ (int (*)[]);                   // { dg-message "previously declared as 'int \\\(\\\*\\\)\\\[]'" "note" }
+void pa_ (int (*)[n]);                  // { dg-warning "\\\[-Wvla-parameter" }
+void pa_ (int (*)[n + 1]);              // { dg-warning "mismatch in bound 1 of argument 1 declared as 'int *\\\(\\\*\\\)\\\[n \\\+ 1\\\]'" }
+
+void ppa_ (int (**)[]);                 // { dg-message "previously declared as 'int \\\(\\\*\\\*\\\)\\\[]'" "note" }
+void ppa_ (int (**)[n]);                // { dg-warning "\\\[-Wvla-parameter" }
+void ppa_ (int (**)[n + 1]);            // { dg-warning "mismatch in bound 1 of argument 1 declared as 'int \\\(\\\*\\\*\\\)\\\[n \\\+ 1\\\]'" }
+
+void pa1 (int (*)[1]);                  // { dg-message "previously declared as 'int \\\(\\\*\\\)\\\[1]'" "note" }
+void pa1 (int (*)[n]);                  // { dg-warning "\\\[-Wvla-parameter" }
+void pa1 (int (*)[1]);
+void pa1 (int (*)[n + 1]);              // { dg-warning "mismatch in bound 1 of argument 1 declared as 'int *\\\(\\\*\\\)\\\[n \\\+ 1\\\]'" }
+
+void ppax (int (**)[*]);                // { dg-message "previously declared as 'int \\\(\\\*\\\*\\\)\\\[.]'" "note" }
+void ppax (int (**)[n]);                // { dg-warning "\\\[-Wvla-parameter" }
+/* A VLA with an unspecified bound is represented the same as [0] so
+   so the pretty printer can't differentiate between the two forms.  */
+void ppax (int (**)[1]);                // { dg-bogus "\\\[-Warray-parameter" "pr?????" { xfail *-*-* } }
+                                        // { dg-warning "\\\[-Wvla-parameter" "pr?????" { xfail *-*-* } .-1 }
+void ppax (int (**)[n + 1]);            // { dg-warning "mismatch in bound 1 of argument 1 declared as 'int *\\\(\\\*\\\*\\\)\\\[n \\\+ 1\\\]'" }
+
+
+void pa1_n (int (*)[1][n]);
+void pa1_n (int (*)[1][n]);
+void pa1_n (int (*)[*][n]);             // { dg-warning "mismatch in bound 1 of argument 1 declared as 'int \\\(\\\*\\\)\\\[\\\*]\\\[n]'" "pr?????" { xfail *-*-*} }
+                                        // { dg-warning "mismatch in bound 1 of argument 1 declared as 'int \\\(\\\*\\\)\\\[0]\\\[n]'" "pr?????" { target *-*-* } .-1 }
+
+void pa1_n_2 (int (*)[1][n][2]);
+void pa1_n_2 (int (*)[1][n][*]);        // { dg-warning "mismatch in bound 3 of argument 1 declared as 'int \\\(\\\*\\\)\\\[1]\\\[n]\\\[\\\*]'" "pr?????" { xfail *-*-* } }
+                                        // { dg-warning "mismatch in bound 3 of argument 1 declared as 'int \\\(\\\*\\\)\\\[1]\\\[n]\\\[0]'" "pr?????" { target *-*-* } .-1 }
+
+
+void pa1_n_2_a1_n_2 (int (*)[1][n][2], int (*)[1][n][2]);
+// { dg-message "previously declared as 'int \\\(\\\*\\\)\\\[1]\\\[n]\\\[2]'" "note" { target *-*-* } .-1 }
+void pa1_n_2_a1_n_2 (int (*)[1][n][2], int (*)[1][n][n]);
+// { dg-warning "mismatch in bound 3 of argument 2 declared as 'int \\\(\\\*\\\)\\\[1]\\\[n]\\\[n]'" "" { target *-*-* } .-1 }
+void pa1_n_2_a1_n_2 (int (*)[1][n][2], int (*)[1][3][2]);
+// { dg-warning "mismatch in bound 2 of argument 2 declared as 'int \\\(\\\*\\\)\\\[1]\\\[3]\\\[2]'" "" { target *-*-* } .-1 }
+void pa1_n_2_a1_n_2 (int (*)[1][n][2], int (*)[n][n][2]);
+// { dg-warning "mismatch in bound 1 of argument 2 declared as 'int \\\(\\\*\\\)\\\[n]\\\[n]\\\[2]'" "" { target *-*-* } .-1 }
+void pa1_n_2_a1_n_2 (int (*)[1][n][n], int (*)[1][n][2]);
+// { dg-warning "mismatch in bound 3 of argument 1 declared as 'int \\\(\\\*\\\)\\\[1]\\\[n]\\\[n]'" "" { target *-*-* } .-1 }
+void pa1_n_2_a1_n_2 (int (*)[n][n][2], int (*)[1][n][2]);
+// { dg-warning "mismatch in bound 1 of argument 1 declared as 'int \\\(\\\*\\\)\\\[n]\\\[n]\\\[2]'" "" { target *-*-* } .-1 }
+void pa1_n_2_a1_n_2 (int (*)[*][*][*], int (*)[*][*][2]);
+// { dg-warning "mismatch in bounds 1, 2, 3 of argument 1 declared as 'int \\\(\\\*\\\)\\\[.]\\\[.]\\\[.]'" "" { target *-*-* } .-1 }
+// { dg-warning "mismatch in bounds 1, 2 of argument 2 declared as 'int \\\(\\\*\\\)\\\[.]\\\[.]\\\[2]'" "" { target *-*-* } .-2 }
+void pa1_n_2_a1_n_2 (int (*)[1][n][2], int (*)[1][n][2]);
+
+/* Verify that pointers to arrays of pointers to arrays...etc are handled
+   correctly.  */
+void pa2pampan (int (*(*(*(*)[2])[m])[n]));
+// { dg-message "previously declared as 'int \\\* \\\(\\\* \\\(\\\* \\\(\\\*\\\)\\\[2]\\\)\\\[m]\\\)\\\[n]'" "note" { target *-*-* } .-1 }
+void pa2pampan (int (*(*(*(*)[2])[m])[1]));
+// { dg-warning "mismatch in bound 3 of argument 1 declared as 'int \\\* \\\(\\\* \\\(\\\* \\\(\\\*\\\)\\\[2]\\\)\\\[m]\\\)\\\[1]'" "" { target *-*-* } .-1  }
+void pa2pampan (int (*(*(*(*)[2])[1])[n]));
+// { dg-warning "mismatch in bound 2 of argument 1 declared as 'int \\\* \\\(\\\* \\\(\\\* \\\(\\\*\\\)\\\[2]\\\)\\\[1]\\\)\\\[n]'" "" { target *-*-* } .-1  }
+void pa2pampan (int (*(*(*(*)[2])[m])[n]));
diff --git a/gcc/testsuite/gcc.dg/Wvla-parameter-4.c b/gcc/testsuite/gcc.dg/Wvla-parameter-4.c
new file mode 100644
index 00000000000..599ad19a3e4
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wvla-parameter-4.c
@@ -0,0 +1,99 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+   Verify warnings for redeclarations of functions with pointer parameters
+   to arrays with variable bounds involving typedefs.
+   { dg-do compile }
+   { dg-options "-Wall -Wvla-parameter" } */
+
+extern int m, n;
+
+typedef int IA3[3];
+
+/* Verify the warning points to the declaration with more unspecified
+   bounds, guiding the user to specify them rather than making them all
+   unspecified.  */
+void* f_pIA3ax (IA3 *x[*]);             // { dg-warning "argument 1 of type 'int \\\(\\\*\\\[\\\*]\\\)\\\[3]' .aka '\[^\n\r\}\]+'. declared with 1 unspecified variable bound" }
+void* f_pIA3ax (IA3 *x[*]);
+void* f_pIA3ax (IA3 *x[n]);             // { dg-message "subsequently declared as 'int \\\(\\\*\\\[n]\\\)\\\[3]' with 0 unspecified variable bounds" "note" }
+void* f_pIA3ax (IA3 *x[n]) { return x; }
+
+
+void* f_pIA3an (IA3 *x[n]);              // { dg-message "previously declared as 'int \\\(\\\*\\\[n]\\\)\\\[3]' with 0 unspecified variable bounds" "note" }
+void* f_pIA3an (IA3 *x[n]);
+void* f_pIA3an (IA3 *x[*]);              // { dg-warning "argument 1 of type 'int \\\(\\\*\\\[\\\*]\\\)\\\[3]' .aka '\[^\n\r\}\]+'. declared with 1 unspecified variable bound" }
+void* f_pIA3an (IA3 *x[n]) { return x; }
+
+
+void nowarn_local_fndecl (void)
+{
+  typedef int IAm[m];
+
+  void* f_IAm (IAm);
+  void* f_IAm (int[m]);
+  void* f_IAm (IAm);
+
+  void* f_iam (int[m]);
+  void* f_iam (IAm);
+  void* f_iam (int[m]);
+
+  typedef int     IA3[3];
+  typedef IA3     IAn_3[n];
+  typedef IAn_3   IA2_n_3[2];
+  typedef IA2_n_3 IAm_2_n_3[m];
+
+  void f_IAm_2_n_3 (IAm_2_n_3);
+  void f_IAm_2_n_3 (IA2_n_3[m]);
+  void f_IAm_2_n_3 (IAn_3[m][2]);
+  void f_IAm_2_n_3 (IA3[m][2][n]);
+  void f_IAm_2_n_3 (int[m][2][n][3]);
+
+  void f_iam_2_n_3 (int[m][2][n][3]);
+  void f_iam_2_n_3 (IA3[m][2][n]);
+  void f_iam_2_n_3 (IAn_3[m][2]);
+  void f_iam_2_n_3 (IAm_2_n_3);
+
+  void f_IAx_m_2_n_3 (IAm_2_n_3[*]);
+  void f_IAx_m_2_n_3 (IA2_n_3[*][m]);
+  void f_IAx_m_2_n_3 (IAn_3[*][m][2]);
+  void f_IAx_m_2_n_3 (IA3[*][m][2][n]);
+  void f_IAx_m_2_n_3 (int[*][m][2][n][3]);
+
+  void f_IA__m_2_n_3 (IAm_2_n_3[]);
+  void f_IA__m_2_n_3 (IA2_n_3[][m]);
+  void f_IA__m_2_n_3 (IAn_3[][m][2]);
+  void f_IA__m_2_n_3 (IA3[][m][2][n]);
+  void f_IA__m_2_n_3 (int[][m][2][n][3]);
+}
+
+
+void warn_local_fndecl (void)
+{
+  typedef int IAm[m];
+  typedef int IAn[n];
+
+  void* g_IAm (IAm);                    // { dg-message "previously declared as 'int\\\[m]' with bound 'm'" }
+  void* g_IAm (int[n]);                 // { dg-warning "argument 1 of type 'int\\\[n]' declared with mismatched bound 'n'" }
+  void* g_IAm (IAm);
+
+  void* g_iam (int[m]);                 // { dg-message "previously declared as 'int\\\[m]' with bound 'm'" }
+  void* g_iam (IAn);                    // { dg-warning "argument 1 of type 'int\\\[n]' declared with mismatched bound 'n'" }
+  void* g_iam (int[m]);
+
+
+  typedef int     IA3[3];
+  typedef IA3     IAn_3[n];
+  typedef IAn_3   IA2_n_3[2];
+  typedef IA2_n_3 IAm_2_n_3[m];
+
+  typedef IA3     IAm_3[m];
+  typedef IAm_3   IA2_m_3[2];
+  typedef IA2_m_3 IAm_2_m_3[m];
+
+  void* g_IAm_2_n_3 (IAm_2_n_3);
+  void* g_IAm_2_n_3 (int[m][2][m][3]);  // { dg-warning "argument 1 of type 'int\\\[m]\\\[2]\\\[m]\\\[3]' declared with mismatched bound 'm'" }
+  void* g_IAm_2_n_3 (IAm_2_n_3);
+
+  void* g_iam_2_n_2 (int[m][2][n][3]);
+  void* g_iam_2_n_2 (IAm_2_m_3);        // { dg-warning "argument 1 of type 'int\\\[m]\\\[2]\\\[m]\\\[3]' declared with mismatched bound 'm'" }
+  void* g_iam_2_n_2 (int[m][2][n][3]);
+}
diff --git a/gcc/testsuite/gcc.dg/Wvla-parameter.c b/gcc/testsuite/gcc.dg/Wvla-parameter.c
new file mode 100644
index 00000000000..6e4df02969c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wvla-parameter.c
@@ -0,0 +1,136 @@
+/* PR c/50584 - No warning for passing small array to C99 static array
+   declarator
+   Verify the -Wvla-parameter warnings correctly diagnose mismatches
+   between one-dimensional VLA and non-VLA arguments in redeclarations
+   of the same function.
+   Also verify that the array/pointer argument form in a mismatched
+   redeclaration doesn't override the form in the initial declaration.
+   { dg-do compile }
+   { dg-options "-Wall -Wvla-parameter" } */
+
+/* Verify that redeclaring an argument as a VLA with an unspecified
+   bound that was first declared as an ordinary array with an unspecified
+   bound triggers a warning.  */
+void f1ia_x (int[]);          // { dg-message "previously declared as an ordinary array 'int\\\[]'" "note" }
+void f1ia_x (int[*]);         // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ia_x (int[]);
+void f1ia_x (int[*]);         // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+/* Also verify that a definition of the same form as the first declaration
+   doesn't trigger a warning and doesn't prevent warnings for subsequent
+   mismatches.  */
+void f1ia_x (int a[]) { (void)&a;}
+void f1ia_x (int[*]);         // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+
+/* Repeat the above but starting with an ordinary array with a constant
+   bound.  */
+void f1ia1x (int[1]);          // { dg-message "previously declared as an ordinary array 'int\\\[1]'" "note" }
+void f1ia1x (int[*]);         // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ia1x (int a[1]) { (void)&a; }
+void f1ia1x (int[1]);
+void f1ia1x (int[*]);         // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+
+void f1ipx (int*);            // { dg-message "previously declared as a pointer 'int ?\\\*'" "note" }
+void f1ipx (int[*]);          // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ipx (int*);
+void f1ipx (int *p) { (void)&p; }
+void f1ipx (int[*]);          // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ipx (int*);
+
+void f2ipx (int*, int*);      // { dg-message "previously declared as a pointer 'int ?\\\*'" "note" }
+void f2ipx (int*, int[*]);    // { dg-warning "argument 2 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f2ipx (int*, int*);
+void f2ipx (int*, int[*]);    // { dg-warning "argument 2 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f2ipx (int *p, int *q) { (void)&p; (void)&q; }
+void f2ipx (int*, int[*]);    // { dg-warning "argument 2 of type 'int\\\[\\\*]' declared as a variable length array" }
+
+void f1ias2x (int[static 2]); // { dg-message "previously declared as an ordinary array 'int\\\[static 2]'" }
+void f1ias2x (int[*]);        // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ias2x (int[static 2]);
+void f1ias2x (int[*]);        // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ias2x (int a[static 2]) { (void)&a; }
+void f1ias2x (int[*]);        // { dg-warning "argument 1 of type 'int\\\[\\\*]' declared as a variable length array" }
+void f1ias2x (int[static 2]);
+
+extern int nelts;
+
+void f1sa_var (short[]);      // { dg-message "previously declared as an ordinary array 'short int\\\[]'" }
+void f1sa_var (short[nelts]); // { dg-warning "argument 1 of type 'short int\\\[nelts]' declared as a variable length array" }
+void f1sa_var (short[]);
+void f1sa_var (short[nelts]); // { dg-warning "argument 1 of type 'short int\\\[nelts]' declared as a variable length array" }
+void f1sa_var (short a[]) { (void)&a; }
+void f1sa_var (short[nelts]); // { dg-warning "argument 1 of type 'short int\\\[nelts]' declared as a variable length array" }
+void f1sa_var (short[]);
+
+void f1sa_expr (int[]);           // { dg-message "previously declared as an ordinary array 'int\\\[]'" }
+void f1sa_expr (int[nelts + 1]);  // { dg-warning "argument 1 of type 'int\\\[nelts \\\+ 1]' declared as a variable length array" }
+void f1sa_expr (int[]);
+void f1sa_expr (int[nelts * 2]);  // { dg-warning "argument 1 of type 'int\\\[nelts \\\* 2]' declared as a variable length array" }
+void f1sa_expr (int a[]) { (void)&a; }
+void f1sa_expr (int[nelts / 3]);  // { dg-warning "argument 1 of type 'int\\\[nelts / 3]' declared as a variable length array" }
+void f1sa_expr (int[]);
+
+extern int f (int);
+
+void f1ia_f (int[]);          // { dg-message "previously declared as an ordinary array 'int\\\[]'" }
+void f1ia_f (int[f (1)]);     // { dg-warning "argument 1 of type 'int\\\[f *\\\(1\\\)]' declared as a variable length array" }
+void f1ia_f (int[]);
+void f1ia_f (int[f (2)]);     // { dg-warning "argument 1 of type 'int\\\[f *\\\(2\\\)]' declared as a variable length array" }
+void f1ia_f (int a[]) { (void)&a; }
+void f1ia_f (int[f (3)]);     // { dg-warning "argument 1 of type 'int\\\[f *\\\(3\\\)]' declared as a variable length array" }
+void f1ia_f (int[f (4)]);     // { dg-warning "argument 1 of type 'int\\\[f *\\\(4\\\)]' declared as a variable length array" }
+void f1ia_f (int[]);
+
+void f1iaf0_f1 (int[f (0)]);  // { dg-message "previously declared as 'int\\\[f *\\\(0\\\)]'" }
+void f1iaf0_f1 (int[f (1)]);  // { dg-warning "argument 1 of type 'int\\\[f *\\\(1\\\)]' declared with mismatched bound" }
+void f1iaf0_f1 (int[f (0)]);
+void f1iaf0_f1 (int[f (1)]);  // { dg-warning "argument 1 of type 'int\\\[f *\\\(1\\\)]' declared with mismatched bound" }
+void f1iaf0_f1 (int a[f (0)]) { (void)&a; }
+void f1iaf0_f1 (int[f (1)]);  // { dg-warning "argument 1 of type 'int\\\[f *\\\(1\\\)]' declared with mismatched bound" }
+void f1iaf0_f1 (int[f (0)]);
+
+void f1la_ (long[]);         // { dg-message "previously declared as an ordinary array 'long int\\\[]'" }
+void f1la_ (long[nelts]);    // { dg-warning "argument 1 of type 'long int\\\[nelts]' declared as a variable length array" }
+void f1la_ (long[]);
+void f1la_ (long a[nelts])   // { dg-warning "argument 1 of type 'long int\\\[nelts]' declared as a variable length array" }
+{ (void)&a; }
+void f1la_ (long[]);
+
+void f2ca_ (int, char[]);     // { dg-message "previously declared as an ordinary array 'char\\\[]'" }
+void f2ca_ (int n, char[n]);  // { dg-warning "argument 2 of type 'char\\\[n]' declared as a variable length array" }
+void f2ca_ (int, char[]);
+void f2ca_ (int n, char a[n]) // { dg-warning "argument 2 of type 'char\\\[n]' declared as a variable length array" }
+{ (void)&n; (void)&a; }
+
+void f2ia1_f (int n, int[n]);     // { dg-message "previously declared as 'int\\\[n]' with bound argument 1" }
+void f2ia1_f (int,   int[f (0)]); // { dg-warning "argument 2 of type 'int\\\[f *\\\(0\\\)]' declared with mismatched bound 'f *\\\(0\\\)'" }
+void f2ia1_f (int m, int[m]);
+void f2ia1_f (int,   int[f (1)]); // { dg-warning "argument 2 of type 'int\\\[f *\\\(1\\\)]' declared with mismatched bound 'f *\\\(1\\\)'" }
+void f2ia1_f (int x, int a[x]) { (void)&x; (void)&a; }
+void f2ia1_f (int,   int[f (2)]);   // { dg-warning "argument 2 of type 'int\\\[f *\\\(2\\\)]' declared with mismatched bound 'f *\\\(2\\\)'" }
+void f2ia1_f (int y, int[y]);
+
+void f2iaf_1 (int,   int[f (0)]); // { dg-message "previously declared as 'int\\\[f *\\\(0\\\)]'" }
+void f2iaf_1 (int n, int[n]);     // { dg-warning "argument 2 of type 'int\\\[n]' declared with mismatched bound argument 1" }
+void f2iaf_1 (int,   int[f (0)]);
+void f2iaf_1 (int m, int[m]);     // { dg-warning "argument 2 of type 'int\\\[m]' declared with mismatched bound argument 1" }
+void f2iaf_1 (int x, int a[f (0)]) { (void)&x; (void)&a; }
+void f2iaf_1 (int y, int[y]);     // { dg-warning "argument 2 of type 'int\\\[y]' declared with mismatched bound argument 1" }
+
+
+void f3ia1 (int n, int, int[n]);  // { dg-message "previously declared as 'int\\\[n]' with bound argument 1" }
+void f3ia1 (int, int n, int[n]);  // { dg-warning "argument 3 of type 'int\\\[n]' declared with mismatched bound argument 2" }
+void f3ia1 (int n, int, int[n]);
+
+
+extern int g (int);
+
+void f1iaf_g (int[f (1)]);    // { dg-message "previously declared as 'int\\\[f *\\\(1\\\)]'" }
+void f1iaf_g (int[g (1)]);    // { dg-warning "argument 1 of type 'int\\\[g *\\\(1\\\)]' declared with mismatched bound" }
+void f1iaf_g (int[f (1)]);
+
+
+void nrf1iaf_g (int[f (1)]);  // { dg-message "previously declared as 'int\\\[f *\\\(1\\\)]'" }
+__attribute__ ((nonnull))
+void nrf1iaf_g (int[g (1)]);  // { dg-warning "argument 1 of type 'int\\\[g *\\\(1\\\)]' declared with mismatched bound" }
+__attribute__ ((noreturn))
+void nrf1iaf_g (int[f (1)]);
diff --git a/gcc/tree-pretty-print.c b/gcc/tree-pretty-print.c
index 655061c174d..ad3dc59bcfe 100644
--- a/gcc/tree-pretty-print.c
+++ b/gcc/tree-pretty-print.c
@@ -1682,9 +1682,9 @@ dump_generic_node (pretty_printer *pp, tree node, int spc, dump_flags_t flags,
 	  pp_string (pp, "atomic ");
 	if (quals & TYPE_QUAL_CONST)
 	  pp_string (pp, "const ");
-	else if (quals & TYPE_QUAL_VOLATILE)
+	if (quals & TYPE_QUAL_VOLATILE)
 	  pp_string (pp, "volatile ");
-	else if (quals & TYPE_QUAL_RESTRICT)
+	if (quals & TYPE_QUAL_RESTRICT)
 	  pp_string (pp, "restrict ");
 
 	if (!ADDR_SPACE_GENERIC_P (TYPE_ADDR_SPACE (node)))

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PING 2][PATCH 2/5] C front end support to detect out-of-bounds accesses to array parameters
  2020-09-16 19:14                         ` Martin Sebor
@ 2020-09-17 22:38                           ` Joseph Myers
  2020-09-20  0:01                             ` Martin Sebor
  0 siblings, 1 reply; 33+ messages in thread
From: Joseph Myers @ 2020-09-17 22:38 UTC (permalink / raw)
  To: Martin Sebor; +Cc: gcc-patches

On Wed, 16 Sep 2020, Martin Sebor via Gcc-patches wrote:

> Attached is an updated revision of the patch.  Besides the tweaks
> above it also contains a cosmetic change to the warning issued
> for mismatches in unspecified VLA bounds: it points at the decl
> with more of them to guide the user to specify them rather than
> make them all unspecified.

The previous version of the patch had a while loop as previously discussed 
to handle skipping multiple consecutive cdk_attrs.

+      next = pd->declarator;
+      while (next && next->kind == cdk_attrs)
+       next = next->declarator;

This version is back to an "if", but I don't see anything else in the new 
version of that function that actually means the "if" would skip multiple 
consecutive cdk_attrs as desired.

The patch is OK with the "while" restored there.  If for some reason the 
"while" breaks something, we'll need to look in more detail at exactly 
what case isn't being handled correctly by "while".

-- 
Joseph S. Myers
joseph@codesourcery.com

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PING 2][PATCH 2/5] C front end support to detect out-of-bounds accesses to array parameters
  2020-09-17 22:38                           ` Joseph Myers
@ 2020-09-20  0:01                             ` Martin Sebor
  2020-09-21 18:20                               ` Vaseeharan Vinayagamoorthy
  0 siblings, 1 reply; 33+ messages in thread
From: Martin Sebor @ 2020-09-20  0:01 UTC (permalink / raw)
  To: Joseph Myers; +Cc: gcc-patches

On 9/17/20 4:38 PM, Joseph Myers wrote:
> On Wed, 16 Sep 2020, Martin Sebor via Gcc-patches wrote:
> 
>> Attached is an updated revision of the patch.  Besides the tweaks
>> above it also contains a cosmetic change to the warning issued
>> for mismatches in unspecified VLA bounds: it points at the decl
>> with more of them to guide the user to specify them rather than
>> make them all unspecified.
> 
> The previous version of the patch had a while loop as previously discussed
> to handle skipping multiple consecutive cdk_attrs.
> 
> +      next = pd->declarator;
> +      while (next && next->kind == cdk_attrs)
> +       next = next->declarator;
> 
> This version is back to an "if", but I don't see anything else in the new
> version of that function that actually means the "if" would skip multiple
> consecutive cdk_attrs as desired.
> 
> The patch is OK with the "while" restored there.  If for some reason the
> "while" breaks something, we'll need to look in more detail at exactly
> what case isn't being handled correctly by "while".

I guess it was the result of an experiment, trying to see if I could
break it with the 'if'.  I (hope I) put it back and pushed the whole
series.  I had to squash patches 1 and 2 because of a dependency that
I had missed.

Thanks for the review, by the way.  I think the signature validation
we've ended up with is quite a bit more comprehensive than the first
attempt.

Martin

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PING 2][PATCH 2/5] C front end support to detect out-of-bounds accesses to array parameters
  2020-09-20  0:01                             ` Martin Sebor
@ 2020-09-21 18:20                               ` Vaseeharan Vinayagamoorthy
  2020-09-21 18:45                                 ` Martin Sebor
  0 siblings, 1 reply; 33+ messages in thread
From: Vaseeharan Vinayagamoorthy @ 2020-09-21 18:20 UTC (permalink / raw)
  To: Martin Sebor, Joseph Myers; +Cc: gcc-patches

After this patch, I am seeing this -Warray-parameter error:

In file included from ../include/pthread.h:1,
                 from ../sysdeps/nptl/thread_db.h:25,
                 from ../nptl/descr.h:32,
                 from ../sysdeps/aarch64/nptl/tls.h:44,
                 from ../include/errno.h:25,
                 from ../sysdeps/unix/sysv/linux/sysdep.h:23,
                 from ../sysdeps/unix/sysv/linux/generic/sysdep.h:22,
                 from ../sysdeps/unix/sysv/linux/aarch64/sysdep.h:24,
                 from <stdin>:1:
../sysdeps/nptl/pthread.h:734:47: error: argument 1 of type ‘struct __jmp_buf_tag *’ declared as a pointer [-Werror=array-parameter=]
  734 | extern int __sigsetjmp (struct __jmp_buf_tag *__env, int __savemask) __THROWNL;
      |                         ~~~~~~~~~~~~~~~~~~~~~~^~~~~
In file included from ../include/setjmp.h:2,
                 from ../nptl/descr.h:24,
                 from ../sysdeps/aarch64/nptl/tls.h:44,
                 from ../include/errno.h:25,
                 from ../sysdeps/unix/sysv/linux/sysdep.h:23,
                 from ../sysdeps/unix/sysv/linux/generic/sysdep.h:22,
                 from ../sysdeps/unix/sysv/linux/aarch64/sysdep.h:24,
                 from <stdin>:1:
../setjmp/setjmp.h:54:46: note: previously declared as an array ‘struct __jmp_buf_tag[1]’
   54 | extern int __sigsetjmp (struct __jmp_buf_tag __env[1], int __savemask) __THROWNL;
      |                         ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~
cc1: all warnings being treated as errors


The build/host/target setup is:
Build: x86_64-linux-gnu (Ubuntu 18.04)
Host: x86_64-linux-gnu
Target: aarch64-none-linux-gnu, aarch64_be-none-linux-gnu, arm-none-linux-gnueabi, arm-none-linux-gnueabihf



Kind regards
Vasee



On 20/09/2020, 01:02, "Gcc-patches on behalf of Martin Sebor via Gcc-patches" <gcc-patches-bounces@gcc.gnu.org on behalf of gcc-patches@gcc.gnu.org> wrote:

    On 9/17/20 4:38 PM, Joseph Myers wrote:
    > On Wed, 16 Sep 2020, Martin Sebor via Gcc-patches wrote:
    > 
    >> Attached is an updated revision of the patch.  Besides the tweaks
    >> above it also contains a cosmetic change to the warning issued
    >> for mismatches in unspecified VLA bounds: it points at the decl
    >> with more of them to guide the user to specify them rather than
    >> make them all unspecified.
    > 
    > The previous version of the patch had a while loop as previously discussed
    > to handle skipping multiple consecutive cdk_attrs.
    > 
    > +      next = pd->declarator;
    > +      while (next && next->kind == cdk_attrs)
    > +       next = next->declarator;
    > 
    > This version is back to an "if", but I don't see anything else in the new
    > version of that function that actually means the "if" would skip multiple
    > consecutive cdk_attrs as desired.
    > 
    > The patch is OK with the "while" restored there.  If for some reason the
    > "while" breaks something, we'll need to look in more detail at exactly
    > what case isn't being handled correctly by "while".

    I guess it was the result of an experiment, trying to see if I could
    break it with the 'if'.  I (hope I) put it back and pushed the whole
    series.  I had to squash patches 1 and 2 because of a dependency that
    I had missed.

    Thanks for the review, by the way.  I think the signature validation
    we've ended up with is quite a bit more comprehensive than the first
    attempt.

    Martin


^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PING 2][PATCH 2/5] C front end support to detect out-of-bounds accesses to array parameters
  2020-09-21 18:20                               ` Vaseeharan Vinayagamoorthy
@ 2020-09-21 18:45                                 ` Martin Sebor
  2020-09-23  8:22                                   ` Szabolcs Nagy
  0 siblings, 1 reply; 33+ messages in thread
From: Martin Sebor @ 2020-09-21 18:45 UTC (permalink / raw)
  To: Vaseeharan Vinayagamoorthy, Joseph Myers; +Cc: gcc-patches

On 9/21/20 12:20 PM, Vaseeharan Vinayagamoorthy wrote:
> After this patch, I am seeing this -Warray-parameter error:
> 
> In file included from ../include/pthread.h:1,
>                   from ../sysdeps/nptl/thread_db.h:25,
>                   from ../nptl/descr.h:32,
>                   from ../sysdeps/aarch64/nptl/tls.h:44,
>                   from ../include/errno.h:25,
>                   from ../sysdeps/unix/sysv/linux/sysdep.h:23,
>                   from ../sysdeps/unix/sysv/linux/generic/sysdep.h:22,
>                   from ../sysdeps/unix/sysv/linux/aarch64/sysdep.h:24,
>                   from <stdin>:1:
> ../sysdeps/nptl/pthread.h:734:47: error: argument 1 of type ‘struct __jmp_buf_tag *’ declared as a pointer [-Werror=array-parameter=]
>    734 | extern int __sigsetjmp (struct __jmp_buf_tag *__env, int __savemask) __THROWNL;
>        |                         ~~~~~~~~~~~~~~~~~~~~~~^~~~~
> In file included from ../include/setjmp.h:2,
>                   from ../nptl/descr.h:24,
>                   from ../sysdeps/aarch64/nptl/tls.h:44,
>                   from ../include/errno.h:25,
>                   from ../sysdeps/unix/sysv/linux/sysdep.h:23,
>                   from ../sysdeps/unix/sysv/linux/generic/sysdep.h:22,
>                   from ../sysdeps/unix/sysv/linux/aarch64/sysdep.h:24,
>                   from <stdin>:1:
> ../setjmp/setjmp.h:54:46: note: previously declared as an array ‘struct __jmp_buf_tag[1]’
>     54 | extern int __sigsetjmp (struct __jmp_buf_tag __env[1], int __savemask) __THROWNL;
>        |                         ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~
> cc1: all warnings being treated as errors

The warning flags differences between the forms of array parameters
in redeclarations of the same function, including pointers vs arrays
as in this instance.  It needs to be suppressed in glibc, either by
making the function declarations consistent, or by #pragma diagnostic.
(IIRC, the pointer declaration comes before struct __jmp_buf_tag has
been defined so simply using the array form there doesn't work without
defining the type first.)

I would expect the warning to be suppressed when using the installed
library thanks to -Wno-system-headers.

Martin

> 
> 
> The build/host/target setup is:
> Build: x86_64-linux-gnu (Ubuntu 18.04)
> Host: x86_64-linux-gnu
> Target: aarch64-none-linux-gnu, aarch64_be-none-linux-gnu, arm-none-linux-gnueabi, arm-none-linux-gnueabihf
> 
> 
> 
> Kind regards
> Vasee
> 
> 
> 
> On 20/09/2020, 01:02, "Gcc-patches on behalf of Martin Sebor via Gcc-patches" <gcc-patches-bounces@gcc.gnu.org on behalf of gcc-patches@gcc.gnu.org> wrote:
> 
>      On 9/17/20 4:38 PM, Joseph Myers wrote:
>      > On Wed, 16 Sep 2020, Martin Sebor via Gcc-patches wrote:
>      >
>      >> Attached is an updated revision of the patch.  Besides the tweaks
>      >> above it also contains a cosmetic change to the warning issued
>      >> for mismatches in unspecified VLA bounds: it points at the decl
>      >> with more of them to guide the user to specify them rather than
>      >> make them all unspecified.
>      >
>      > The previous version of the patch had a while loop as previously discussed
>      > to handle skipping multiple consecutive cdk_attrs.
>      >
>      > +      next = pd->declarator;
>      > +      while (next && next->kind == cdk_attrs)
>      > +       next = next->declarator;
>      >
>      > This version is back to an "if", but I don't see anything else in the new
>      > version of that function that actually means the "if" would skip multiple
>      > consecutive cdk_attrs as desired.
>      >
>      > The patch is OK with the "while" restored there.  If for some reason the
>      > "while" breaks something, we'll need to look in more detail at exactly
>      > what case isn't being handled correctly by "while".
> 
>      I guess it was the result of an experiment, trying to see if I could
>      break it with the 'if'.  I (hope I) put it back and pushed the whole
>      series.  I had to squash patches 1 and 2 because of a dependency that
>      I had missed.
> 
>      Thanks for the review, by the way.  I think the signature validation
>      we've ended up with is quite a bit more comprehensive than the first
>      attempt.
> 
>      Martin
> 


^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PING 2][PATCH 2/5] C front end support to detect out-of-bounds accesses to array parameters
  2020-09-21 18:45                                 ` Martin Sebor
@ 2020-09-23  8:22                                   ` Szabolcs Nagy
  2020-09-23 15:44                                     ` Szabolcs Nagy
  0 siblings, 1 reply; 33+ messages in thread
From: Szabolcs Nagy @ 2020-09-23  8:22 UTC (permalink / raw)
  To: Martin Sebor; +Cc: Vaseeharan Vinayagamoorthy, Joseph Myers, gcc-patches

The 09/21/2020 12:45, Martin Sebor via Gcc-patches wrote:
> On 9/21/20 12:20 PM, Vaseeharan Vinayagamoorthy wrote:
> > After this patch, I am seeing this -Warray-parameter error:
> > 
> > In file included from ../include/pthread.h:1,
> >                   from ../sysdeps/nptl/thread_db.h:25,
> >                   from ../nptl/descr.h:32,
> >                   from ../sysdeps/aarch64/nptl/tls.h:44,
> >                   from ../include/errno.h:25,
> >                   from ../sysdeps/unix/sysv/linux/sysdep.h:23,
> >                   from ../sysdeps/unix/sysv/linux/generic/sysdep.h:22,
> >                   from ../sysdeps/unix/sysv/linux/aarch64/sysdep.h:24,
> >                   from <stdin>:1:
> > ../sysdeps/nptl/pthread.h:734:47: error: argument 1 of type ‘struct __jmp_buf_tag *’ declared as a pointer [-Werror=array-parameter=]
> >    734 | extern int __sigsetjmp (struct __jmp_buf_tag *__env, int __savemask) __THROWNL;
> >        |                         ~~~~~~~~~~~~~~~~~~~~~~^~~~~
> > In file included from ../include/setjmp.h:2,
> >                   from ../nptl/descr.h:24,
> >                   from ../sysdeps/aarch64/nptl/tls.h:44,
> >                   from ../include/errno.h:25,
> >                   from ../sysdeps/unix/sysv/linux/sysdep.h:23,
> >                   from ../sysdeps/unix/sysv/linux/generic/sysdep.h:22,
> >                   from ../sysdeps/unix/sysv/linux/aarch64/sysdep.h:24,
> >                   from <stdin>:1:
> > ../setjmp/setjmp.h:54:46: note: previously declared as an array ‘struct __jmp_buf_tag[1]’
> >     54 | extern int __sigsetjmp (struct __jmp_buf_tag __env[1], int __savemask) __THROWNL;
> >        |                         ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~
> > cc1: all warnings being treated as errors
> 
> The warning flags differences between the forms of array parameters
> in redeclarations of the same function, including pointers vs arrays
> as in this instance.  It needs to be suppressed in glibc, either by
> making the function declarations consistent, or by #pragma diagnostic.
> (IIRC, the pointer declaration comes before struct __jmp_buf_tag has
> been defined so simply using the array form there doesn't work without
> defining the type first.)
> 
> I would expect the warning to be suppressed when using the installed
> library thanks to -Wno-system-headers.

why is this a warning? i'm not convinced it
should be in -Wall.

this is a warning with false positives that
have no portable workaround and does not
really help catching bugs (at least i doubt
inconsistent array vs pointer declaration
is causing common problems).

what gcc should warn about is if there is an
array style argument declaration and a caller
passes an array that's provably shorter than
that.

pointer style declaration should be possible
to mix with array style (this is important
for standard headers but any api with multiple
implementations can end up in situations where
both array and pointer style decl will be
present).


^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PING 2][PATCH 2/5] C front end support to detect out-of-bounds accesses to array parameters
  2020-09-23  8:22                                   ` Szabolcs Nagy
@ 2020-09-23 15:44                                     ` Szabolcs Nagy
  2020-09-23 17:45                                       ` Martin Sebor
  0 siblings, 1 reply; 33+ messages in thread
From: Szabolcs Nagy @ 2020-09-23 15:44 UTC (permalink / raw)
  To: Martin Sebor; +Cc: gcc-patches, Vaseeharan Vinayagamoorthy, Joseph Myers

The 09/23/2020 09:22, Szabolcs Nagy wrote:
> The 09/21/2020 12:45, Martin Sebor via Gcc-patches wrote:
> > On 9/21/20 12:20 PM, Vaseeharan Vinayagamoorthy wrote:
> > > After this patch, I am seeing this -Warray-parameter error:
> > > 
> > > In file included from ../include/pthread.h:1,
> > >                   from ../sysdeps/nptl/thread_db.h:25,
> > >                   from ../nptl/descr.h:32,
> > >                   from ../sysdeps/aarch64/nptl/tls.h:44,
> > >                   from ../include/errno.h:25,
> > >                   from ../sysdeps/unix/sysv/linux/sysdep.h:23,
> > >                   from ../sysdeps/unix/sysv/linux/generic/sysdep.h:22,
> > >                   from ../sysdeps/unix/sysv/linux/aarch64/sysdep.h:24,
> > >                   from <stdin>:1:
> > > ../sysdeps/nptl/pthread.h:734:47: error: argument 1 of type ‘struct __jmp_buf_tag *’ declared as a pointer [-Werror=array-parameter=]
> > >    734 | extern int __sigsetjmp (struct __jmp_buf_tag *__env, int __savemask) __THROWNL;
> > >        |                         ~~~~~~~~~~~~~~~~~~~~~~^~~~~
> > > In file included from ../include/setjmp.h:2,
> > >                   from ../nptl/descr.h:24,
> > >                   from ../sysdeps/aarch64/nptl/tls.h:44,
> > >                   from ../include/errno.h:25,
> > >                   from ../sysdeps/unix/sysv/linux/sysdep.h:23,
> > >                   from ../sysdeps/unix/sysv/linux/generic/sysdep.h:22,
> > >                   from ../sysdeps/unix/sysv/linux/aarch64/sysdep.h:24,
> > >                   from <stdin>:1:
> > > ../setjmp/setjmp.h:54:46: note: previously declared as an array ‘struct __jmp_buf_tag[1]’
> > >     54 | extern int __sigsetjmp (struct __jmp_buf_tag __env[1], int __savemask) __THROWNL;
> > >        |                         ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~
> > > cc1: all warnings being treated as errors
> > 
> > The warning flags differences between the forms of array parameters
> > in redeclarations of the same function, including pointers vs arrays
> > as in this instance.  It needs to be suppressed in glibc, either by
> > making the function declarations consistent, or by #pragma diagnostic.
> > (IIRC, the pointer declaration comes before struct __jmp_buf_tag has
> > been defined so simply using the array form there doesn't work without
> > defining the type first.)
> > 
> > I would expect the warning to be suppressed when using the installed
> > library thanks to -Wno-system-headers.
> 
> why is this a warning? i'm not convinced it
> should be in -Wall.
> 
> this is a warning with false positives that
> have no portable workaround and does not
> really help catching bugs (at least i doubt
> inconsistent array vs pointer declaration
> is causing common problems).
> 
> what gcc should warn about is if there is an
> array style argument declaration and a caller
> passes an array that's provably shorter than
> that.

i take this back: such warning only makes
sense when the static keyword is used in
the array declarator.

i really think this issue should be fixed
in gcc and not in glibc: it's not true that
the argument has array type, the standard
requires the parameter type to be adjusted:

  A declaration of a parameter as "array of type"
  shall be adjusted to "qualified pointer to type"

this warning will just confuse users and
make them believe that the two declaration
styles for pointer arguments are somehow
different.

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PING 2][PATCH 2/5] C front end support to detect out-of-bounds accesses to array parameters
  2020-09-23 15:44                                     ` Szabolcs Nagy
@ 2020-09-23 17:45                                       ` Martin Sebor
  2020-09-24  3:45                                         ` Jeff Law
  0 siblings, 1 reply; 33+ messages in thread
From: Martin Sebor @ 2020-09-23 17:45 UTC (permalink / raw)
  To: Szabolcs Nagy; +Cc: gcc-patches, Vaseeharan Vinayagamoorthy, Joseph Myers

On 9/23/20 9:44 AM, Szabolcs Nagy wrote:
> The 09/23/2020 09:22, Szabolcs Nagy wrote:
>> The 09/21/2020 12:45, Martin Sebor via Gcc-patches wrote:
>>> On 9/21/20 12:20 PM, Vaseeharan Vinayagamoorthy wrote:
>>>> After this patch, I am seeing this -Warray-parameter error:
>>>>
>>>> In file included from ../include/pthread.h:1,
>>>>                    from ../sysdeps/nptl/thread_db.h:25,
>>>>                    from ../nptl/descr.h:32,
>>>>                    from ../sysdeps/aarch64/nptl/tls.h:44,
>>>>                    from ../include/errno.h:25,
>>>>                    from ../sysdeps/unix/sysv/linux/sysdep.h:23,
>>>>                    from ../sysdeps/unix/sysv/linux/generic/sysdep.h:22,
>>>>                    from ../sysdeps/unix/sysv/linux/aarch64/sysdep.h:24,
>>>>                    from <stdin>:1:
>>>> ../sysdeps/nptl/pthread.h:734:47: error: argument 1 of type ‘struct __jmp_buf_tag *’ declared as a pointer [-Werror=array-parameter=]
>>>>     734 | extern int __sigsetjmp (struct __jmp_buf_tag *__env, int __savemask) __THROWNL;
>>>>         |                         ~~~~~~~~~~~~~~~~~~~~~~^~~~~
>>>> In file included from ../include/setjmp.h:2,
>>>>                    from ../nptl/descr.h:24,
>>>>                    from ../sysdeps/aarch64/nptl/tls.h:44,
>>>>                    from ../include/errno.h:25,
>>>>                    from ../sysdeps/unix/sysv/linux/sysdep.h:23,
>>>>                    from ../sysdeps/unix/sysv/linux/generic/sysdep.h:22,
>>>>                    from ../sysdeps/unix/sysv/linux/aarch64/sysdep.h:24,
>>>>                    from <stdin>:1:
>>>> ../setjmp/setjmp.h:54:46: note: previously declared as an array ‘struct __jmp_buf_tag[1]’
>>>>      54 | extern int __sigsetjmp (struct __jmp_buf_tag __env[1], int __savemask) __THROWNL;
>>>>         |                         ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~
>>>> cc1: all warnings being treated as errors
>>>
>>> The warning flags differences between the forms of array parameters
>>> in redeclarations of the same function, including pointers vs arrays
>>> as in this instance.  It needs to be suppressed in glibc, either by
>>> making the function declarations consistent, or by #pragma diagnostic.
>>> (IIRC, the pointer declaration comes before struct __jmp_buf_tag has
>>> been defined so simply using the array form there doesn't work without
>>> defining the type first.)
>>>
>>> I would expect the warning to be suppressed when using the installed
>>> library thanks to -Wno-system-headers.
>>
>> why is this a warning? i'm not convinced it
>> should be in -Wall.

The main motivation for the warning is to detect unintentional
inconsistencies between function redeclarations that make deriving
true true intent difficult or impossible  (e.g, T[3] vs T[1], or
T[] vs T[1], or equivalently T* vs T[1]).

One goal is to support the convention where a constant array bound
in a function array parameter is used in lieu of the [static N]
notation (i.e., the minimum number of elements the caller is
expected to  make available).  The [static N] notation is little
known, used only exceedingly rarely, and isn't available in C++.
The array notation is used more often, although by no means common.

The ultimate goal is to motivate users to take advantage of GCC's
ability to check ordinary functions for out-of-bounds accesses to
array arguments.  The checking is only feasible if all declarations
of the same function, including its definition, use a consistent
notation to specify the same bound.  Including the strict
-Warray-parameter=2 setting in -Wall helps support this goal
(-Warray-parameter=1 doesn't warn for mismatches in the forms
of ordinary array bounds without [static].)

I mentioned the results of testing the patch with a number of
packages, including Glibc, Binutils/GDB, Glibc, and the kernel,
in the overview of the patch series:
https://gcc.gnu.org/pipermail/gcc-patches/2020-July/550920.html
It explains why I chose not to relax the warning to accommodate
the Glibc use case.

Based on my admittedly limited testing I'm not concerned about
the warning having adverse effects.  But if broader excposure
shows that it is prone to some it can certainly be adjusted.
Jeff does periodic mass rebuilds of all of Fedora with the top
of GCC trunk so we should know soon.

Martin

>>
>> this is a warning with false positives that
>> have no portable workaround and does not
>> really help catching bugs (at least i doubt
>> inconsistent array vs pointer declaration
>> is causing common problems).
>>
>> what gcc should warn about is if there is an
>> array style argument declaration and a caller
>> passes an array that's provably shorter than
>> that.
> 
> i take this back: such warning only makes
> sense when the static keyword is used in
> the array declarator.
> 
> i really think this issue should be fixed
> in gcc and not in glibc: it's not true that
> the argument has array type, the standard
> requires the parameter type to be adjusted:
> 
>    A declaration of a parameter as "array of type"
>    shall be adjusted to "qualified pointer to type"
> 
> this warning will just confuse users and
> make them believe that the two declaration
> styles for pointer arguments are somehow
> different.
> 


^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PING 2][PATCH 2/5] C front end support to detect out-of-bounds accesses to array parameters
  2020-09-23 17:45                                       ` Martin Sebor
@ 2020-09-24  3:45                                         ` Jeff Law
  2020-10-05  8:45                                           ` Szabolcs Nagy
  0 siblings, 1 reply; 33+ messages in thread
From: Jeff Law @ 2020-09-24  3:45 UTC (permalink / raw)
  To: Martin Sebor, Szabolcs Nagy
  Cc: gcc-patches, Vaseeharan Vinayagamoorthy, Joseph Myers


On 9/23/20 11:45 AM, Martin Sebor via Gcc-patches wrote:
> On 9/23/20 9:44 AM, Szabolcs Nagy wrote:
>> The 09/23/2020 09:22, Szabolcs Nagy wrote:
>>> The 09/21/2020 12:45, Martin Sebor via Gcc-patches wrote:
>>>> On 9/21/20 12:20 PM, Vaseeharan Vinayagamoorthy wrote:
>>>>> After this patch, I am seeing this -Warray-parameter error:
>>>>>
>>>>> In file included from ../include/pthread.h:1,
>>>>>                    from ../sysdeps/nptl/thread_db.h:25,
>>>>>                    from ../nptl/descr.h:32,
>>>>>                    from ../sysdeps/aarch64/nptl/tls.h:44,
>>>>>                    from ../include/errno.h:25,
>>>>>                    from ../sysdeps/unix/sysv/linux/sysdep.h:23,
>>>>>                    from 
>>>>> ../sysdeps/unix/sysv/linux/generic/sysdep.h:22,
>>>>>                    from 
>>>>> ../sysdeps/unix/sysv/linux/aarch64/sysdep.h:24,
>>>>>                    from <stdin>:1:
>>>>> ../sysdeps/nptl/pthread.h:734:47: error: argument 1 of type 
>>>>> ‘struct __jmp_buf_tag *’ declared as a pointer 
>>>>> [-Werror=array-parameter=]
>>>>>     734 | extern int __sigsetjmp (struct __jmp_buf_tag *__env, int 
>>>>> __savemask) __THROWNL;
>>>>>         | ~~~~~~~~~~~~~~~~~~~~~~^~~~~
>>>>> In file included from ../include/setjmp.h:2,
>>>>>                    from ../nptl/descr.h:24,
>>>>>                    from ../sysdeps/aarch64/nptl/tls.h:44,
>>>>>                    from ../include/errno.h:25,
>>>>>                    from ../sysdeps/unix/sysv/linux/sysdep.h:23,
>>>>>                    from 
>>>>> ../sysdeps/unix/sysv/linux/generic/sysdep.h:22,
>>>>>                    from 
>>>>> ../sysdeps/unix/sysv/linux/aarch64/sysdep.h:24,
>>>>>                    from <stdin>:1:
>>>>> ../setjmp/setjmp.h:54:46: note: previously declared as an array 
>>>>> ‘struct __jmp_buf_tag[1]’
>>>>>      54 | extern int __sigsetjmp (struct __jmp_buf_tag __env[1], 
>>>>> int __savemask) __THROWNL;
>>>>>         | ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~
>>>>> cc1: all warnings being treated as errors
>>>>
>>>> The warning flags differences between the forms of array parameters
>>>> in redeclarations of the same function, including pointers vs arrays
>>>> as in this instance.  It needs to be suppressed in glibc, either by
>>>> making the function declarations consistent, or by #pragma diagnostic.
>>>> (IIRC, the pointer declaration comes before struct __jmp_buf_tag has
>>>> been defined so simply using the array form there doesn't work without
>>>> defining the type first.)
>>>>
>>>> I would expect the warning to be suppressed when using the installed
>>>> library thanks to -Wno-system-headers.
>>>
>>> why is this a warning? i'm not convinced it
>>> should be in -Wall.
>
> The main motivation for the warning is to detect unintentional
> inconsistencies between function redeclarations that make deriving
> true true intent difficult or impossible  (e.g, T[3] vs T[1], or
> T[] vs T[1], or equivalently T* vs T[1]).
>
> One goal is to support the convention where a constant array bound
> in a function array parameter is used in lieu of the [static N]
> notation (i.e., the minimum number of elements the caller is
> expected to  make available).  The [static N] notation is little
> known, used only exceedingly rarely, and isn't available in C++.
> The array notation is used more often, although by no means common.
>
> The ultimate goal is to motivate users to take advantage of GCC's
> ability to check ordinary functions for out-of-bounds accesses to
> array arguments.  The checking is only feasible if all declarations
> of the same function, including its definition, use a consistent
> notation to specify the same bound.  Including the strict
> -Warray-parameter=2 setting in -Wall helps support this goal
> (-Warray-parameter=1 doesn't warn for mismatches in the forms
> of ordinary array bounds without [static].)
>
> I mentioned the results of testing the patch with a number of
> packages, including Glibc, Binutils/GDB, Glibc, and the kernel,
> in the overview of the patch series:
> https://gcc.gnu.org/pipermail/gcc-patches/2020-July/550920.html
> It explains why I chose not to relax the warning to accommodate
> the Glibc use case.
>
> Based on my admittedly limited testing I'm not concerned about
> the warning having adverse effects.  But if broader excposure
> shows that it is prone to some it can certainly be adjusted.
> Jeff does periodic mass rebuilds of all of Fedora with the top
> of GCC trunk so we should know soon.

Yea.  If the patch was in last week's snapshot, then it should start 
spinning the 9000 Fedora packages later tonight alongside some Ranger 
bits we're testing.  I'm on PTO till Monday though, so I won't be 
looking at the results until Tuesday probably...


jeff



^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PING 2][PATCH 2/5] C front end support to detect out-of-bounds accesses to array parameters
  2020-09-24  3:45                                         ` Jeff Law
@ 2020-10-05  8:45                                           ` Szabolcs Nagy
  0 siblings, 0 replies; 33+ messages in thread
From: Szabolcs Nagy @ 2020-10-05  8:45 UTC (permalink / raw)
  To: Jeff Law
  Cc: Martin Sebor, gcc-patches, Vaseeharan Vinayagamoorthy, Joseph Myers

The 09/23/2020 21:45, Jeff Law wrote:
> On 9/23/20 11:45 AM, Martin Sebor via Gcc-patches wrote:
> > On 9/23/20 9:44 AM, Szabolcs Nagy wrote:
> > > The 09/23/2020 09:22, Szabolcs Nagy wrote:
> > > > The 09/21/2020 12:45, Martin Sebor via Gcc-patches wrote:
> > > > > On 9/21/20 12:20 PM, Vaseeharan Vinayagamoorthy wrote:
> > > > > > After this patch, I am seeing this -Warray-parameter error:
> > > > > > 
> > > > > > In file included from ../include/pthread.h:1,
> > > > > >                    from ../sysdeps/nptl/thread_db.h:25,
> > > > > >                    from ../nptl/descr.h:32,
> > > > > >                    from ../sysdeps/aarch64/nptl/tls.h:44,
> > > > > >                    from ../include/errno.h:25,
> > > > > >                    from ../sysdeps/unix/sysv/linux/sysdep.h:23,
> > > > > >                    from
> > > > > > ../sysdeps/unix/sysv/linux/generic/sysdep.h:22,
> > > > > >                    from
> > > > > > ../sysdeps/unix/sysv/linux/aarch64/sysdep.h:24,
> > > > > >                    from <stdin>:1:
> > > > > > ../sysdeps/nptl/pthread.h:734:47: error: argument 1 of
> > > > > > type ‘struct __jmp_buf_tag *’ declared as a pointer
> > > > > > [-Werror=array-parameter=]
> > > > > >     734 | extern int __sigsetjmp (struct __jmp_buf_tag
> > > > > > *__env, int __savemask) __THROWNL;
> > > > > >         | ~~~~~~~~~~~~~~~~~~~~~~^~~~~
> > > > > > In file included from ../include/setjmp.h:2,
> > > > > >                    from ../nptl/descr.h:24,
> > > > > >                    from ../sysdeps/aarch64/nptl/tls.h:44,
> > > > > >                    from ../include/errno.h:25,
> > > > > >                    from ../sysdeps/unix/sysv/linux/sysdep.h:23,
> > > > > >                    from
> > > > > > ../sysdeps/unix/sysv/linux/generic/sysdep.h:22,
> > > > > >                    from
> > > > > > ../sysdeps/unix/sysv/linux/aarch64/sysdep.h:24,
> > > > > >                    from <stdin>:1:
> > > > > > ../setjmp/setjmp.h:54:46: note: previously declared as
> > > > > > an array ‘struct __jmp_buf_tag[1]’
> > > > > >      54 | extern int __sigsetjmp (struct __jmp_buf_tag
> > > > > > __env[1], int __savemask) __THROWNL;
> > > > > >         | ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~
> > > > > > cc1: all warnings being treated as errors
> > > > > 
> > > > > The warning flags differences between the forms of array parameters
> > > > > in redeclarations of the same function, including pointers vs arrays
> > > > > as in this instance.  It needs to be suppressed in glibc, either by
> > > > > making the function declarations consistent, or by #pragma diagnostic.
> > > > > (IIRC, the pointer declaration comes before struct __jmp_buf_tag has
> > > > > been defined so simply using the array form there doesn't work without
> > > > > defining the type first.)
> > > > > 
> > > > > I would expect the warning to be suppressed when using the installed
> > > > > library thanks to -Wno-system-headers.
> > > > 
> > > > why is this a warning? i'm not convinced it
> > > > should be in -Wall.
> > 
> > The main motivation for the warning is to detect unintentional
> > inconsistencies between function redeclarations that make deriving
> > true true intent difficult or impossible  (e.g, T[3] vs T[1], or
> > T[] vs T[1], or equivalently T* vs T[1]).
> > 
> > One goal is to support the convention where a constant array bound
> > in a function array parameter is used in lieu of the [static N]
> > notation (i.e., the minimum number of elements the caller is
> > expected to  make available).  The [static N] notation is little
> > known, used only exceedingly rarely, and isn't available in C++.
> > The array notation is used more often, although by no means common.
> > 
> > The ultimate goal is to motivate users to take advantage of GCC's
> > ability to check ordinary functions for out-of-bounds accesses to
> > array arguments.  The checking is only feasible if all declarations
> > of the same function, including its definition, use a consistent
> > notation to specify the same bound.  Including the strict
> > -Warray-parameter=2 setting in -Wall helps support this goal
> > (-Warray-parameter=1 doesn't warn for mismatches in the forms
> > of ordinary array bounds without [static].)
> > 
> > I mentioned the results of testing the patch with a number of
> > packages, including Glibc, Binutils/GDB, Glibc, and the kernel,
> > in the overview of the patch series:
> > https://gcc.gnu.org/pipermail/gcc-patches/2020-July/550920.html
> > It explains why I chose not to relax the warning to accommodate
> > the Glibc use case.
> > 
> > Based on my admittedly limited testing I'm not concerned about
> > the warning having adverse effects.  But if broader excposure
> > shows that it is prone to some it can certainly be adjusted.
> > Jeff does periodic mass rebuilds of all of Fedora with the top
> > of GCC trunk so we should know soon.
> 
> Yea.  If the patch was in last week's snapshot, then it should start
> spinning the 9000 Fedora packages later tonight alongside some Ranger bits
> we're testing.  I'm on PTO till Monday though, so I won't be looking at the
> results until Tuesday probably...

any news on this?

i still think that the warning is only appropriate
for [static N] and not [N], (and that the main reason
static N is not used is because compilers ignore it.)

so i'd like to see if the warning is causing problems
in practice before fixing glibc.

^ permalink raw reply	[flat|nested] 33+ messages in thread

end of thread, other threads:[~2020-10-05  8:45 UTC | newest]

Thread overview: 33+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-07-29  1:13 [PATCH 0/5] add checking of function array parameters (PR 50584) Martin Sebor
2020-07-29  1:16 ` [PATCH 1/5] infrastructure to detect out-of-bounds accesses to array parameters Martin Sebor
2020-08-07 17:08   ` Martin Sebor
2020-08-13 19:09     ` Jeff Law
2020-07-29  1:19 ` [PATCH 2/5] C front end support " Martin Sebor
2020-07-29 18:12   ` Joseph Myers
2020-08-07 17:01     ` Martin Sebor
2020-08-12 23:19       ` Joseph Myers
2020-08-13 23:04         ` Martin Sebor
2020-08-17 22:09           ` Joseph Myers
2020-08-19 22:56             ` Martin Sebor
2020-08-20  0:09               ` Joseph Myers
2020-08-21 19:17                 ` Martin Sebor
2020-08-25 18:44                 ` Martin Sebor
2020-09-03  0:03                   ` [PING][PATCH " Martin Sebor
2020-09-09 21:39                     ` [PING 2][PATCH " Martin Sebor
2020-09-15 23:02                       ` Joseph Myers
2020-09-16 19:14                         ` Martin Sebor
2020-09-17 22:38                           ` Joseph Myers
2020-09-20  0:01                             ` Martin Sebor
2020-09-21 18:20                               ` Vaseeharan Vinayagamoorthy
2020-09-21 18:45                                 ` Martin Sebor
2020-09-23  8:22                                   ` Szabolcs Nagy
2020-09-23 15:44                                     ` Szabolcs Nagy
2020-09-23 17:45                                       ` Martin Sebor
2020-09-24  3:45                                         ` Jeff Law
2020-10-05  8:45                                           ` Szabolcs Nagy
2020-07-29  1:20 ` [PATCH 0/5] add checking of function array parameters (PR 50584) Martin Sebor
2020-08-13 16:26   ` Jeff Law
2020-07-29  1:22 ` [PATCH 4/5] - extend -Wstringop-overflow to detect out-of-bounds accesses to array parameters Martin Sebor
2020-08-13 16:31   ` Jeff Law
2020-07-29  1:24 ` [PATCH 5/5] extend -Warray-bounds " Martin Sebor
2020-08-13 16:49   ` Jeff Law

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).