diff mbox series

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

Message ID f884af36-7c1a-b230-617f-41dcdd73e038@gmail.com
State New
Headers show
Series add checking of function array parameters (PR 50584) | expand

Commit Message

Martin Sebor July 29, 2020, 1:16 a.m. UTC
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.

Comments

Martin Sebor Aug. 7, 2020, 5:08 p.m. UTC | #1
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
Li, Pan2 via Gcc-patches Aug. 13, 2020, 7:09 p.m. UTC | #2
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
>
diff mbox series

Patch

[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;