[v3] add object access attributes (PR 83859)
diff mbox series

Message ID 96a09235-ad6e-4ee4-8a7e-ac3fee688171@gmail.com
State New
Headers show
Series
  • [v3] add object access attributes (PR 83859)
Related show

Commit Message

Martin Sebor Nov. 21, 2019, 5:11 p.m. UTC
Attached is another revision of this enhancement, this one
incorporating Richard's request for a more efficient encoding
of the attributes to enable faster parsing.  There is just one
attribute called access, with the rest being arguments.  This
is transformed to access (mode-string) where the mode-string
is a STRING_CST describing the access mode (read/write/both)
and the two positional arguments for the function type.

I have also removed the attributes from the built-in functions
since they're not used for anything yet, and added more argument
validation.

To test this a little more extensively, I annotated a few Glibc
<unistd.h> functions with the new attribute and rebuilt it and
its test suite.  That exposed a problem with function pointers
not being handled correctly so I fixed that by letting
the access attribute apply to function pointers (and function
pointer types in general).

When this is finalized, if there's time I'm still hoping to get
back to the parts of the patch that make use of the attribute
for -Wunused and -Wuninitialized that I removed on Jeff's request
for smaller, independent changes.

In GCC 11 I'd like to look into tying this attribute in with
_FORTIFY_SOURCE.

Martin

Comments

Jeff Law Nov. 21, 2019, 10:38 p.m. UTC | #1
On 11/21/19 10:11 AM, Martin Sebor wrote:
> Attached is another revision of this enhancement, this one
> incorporating Richard's request for a more efficient encoding
> of the attributes to enable faster parsing.  There is just one
> attribute called access, with the rest being arguments.  This
> is transformed to access (mode-string) where the mode-string
> is a STRING_CST describing the access mode (read/write/both)
> and the two positional arguments for the function type.
> 
> I have also removed the attributes from the built-in functions
> since they're not used for anything yet, and added more argument
> validation.
> 
> To test this a little more extensively, I annotated a few Glibc
> <unistd.h> functions with the new attribute and rebuilt it and
> its test suite.  That exposed a problem with function pointers
> not being handled correctly so I fixed that by letting
> the access attribute apply to function pointers (and function
> pointer types in general).
> 
> When this is finalized, if there's time I'm still hoping to get
> back to the parts of the patch that make use of the attribute
> for -Wunused and -Wuninitialized that I removed on Jeff's request
> for smaller, independent changes.
> 
> In GCC 11 I'd like to look into tying this attribute in with
> _FORTIFY_SOURCE.
> 
> Martin
> 
> gcc-83859.diff
> 
> PR middle-end/83859 - attributes to associate pointer arguments and sizes
> 
> gcc/ChangeLog:
> 
> 	PR middle-end/83859
> 	* attribs.h (struct attr_access): New.
> 	* attribs.c (decl_attributes): Add an informational note.
> 	* builtins.c (check_access): Make extern.  Consistently set no-warning
> 	after issuing a warning.  Handle calls through function pointers.  Set
> 	no-warning.
> 	* builtins.h (check_access): Declare.
> 	* calls.c (rdwr_access_hash): New type.
> 	(rdwr_map): Same.
> 	(init_attr_rdwr_indices): New function.
> 	(maybe_warn_rdwr_sizes): Same.
> 	(initialize_argument_information): Call init_attr_rdwr_indices.
> 	Call maybe_warn_rdwr_sizes.
> 	* doc/extend.texi (attribute access): Document new attribute.
> 
> gcc/c-family/ChangeLog:
> 
> 	PR middle-end/83859
> 	* c-attribs.c (handle_access_attribute): New function.
> 	(c_common_attribute_table): Add new attribute.
> 	(get_argument_type): New function.
> 	(append_access_attrs): New function.
> 	(get_nonnull_operand): Rename...
> 	(get_attribute_operand): ...to this.
> 	* c-common.c (get_nonnull_operand): Rename...
> 	(get_attribute_operand): ...to this.
> 
> gcc/testsuite/ChangeLog:
> 
> 	PR middle-end/83859
> 	* c-c++-common/attr-nonstring-8.c: Adjust text of expected warning.
> 	* gcc.dg/Wstringop-overflow-22.c: New test.
> 	* gcc.dg/Wstringop-overflow-23.c: New test.
> 	* gcc.dg/attr-access-read-only.c: New test.
> 	* gcc.dg/attr-access-read-write.c: New test.
> 	* gcc.dg/attr-access-read-write-2.c: New test.
> 	* gcc.dg/attr-access-write-only.c: New test.
> 

> diff --git a/gcc/calls.c b/gcc/calls.c
> index 62921351b11..15627abbd0d 100644
> --- a/gcc/calls.c
> +++ b/gcc/calls.c
> @@ -52,6 +52,8 @@ along with GCC; see the file COPYING3.  If not see
>  #include "tree-ssa-strlen.h"
>  #include "intl.h"
>  #include "stringpool.h"
> +#include "hash-map.h"
> +#include "hash-traits.h"
>  #include "attribs.h"
>  #include "builtins.h"
>  #include "gimple-fold.h"
> @@ -1258,6 +1260,9 @@ alloc_max_size (void)
>  bool
>  get_size_range (tree exp, tree range[2], bool allow_zero /* = false */)
>  {
> +  if (!exp)
> +    return false;
> +
>    if (tree_fits_uhwi_p (exp))
>      {
>        /* EXP is a constant.  */
This change isn't mentioned anywhere in the ChangeLog.  If its
intentional, please mention it in the ChangeLog.  If it's not
intentional, then drop it :-)



> diff --git a/gcc/gimple.h b/gcc/gimple.h
> index 5a190b1714d..10223386d04 100644
> --- a/gcc/gimple.h
> +++ b/gcc/gimple.h
> @@ -24,6 +24,8 @@ along with GCC; see the file COPYING3.  If not see
>  
>  #include "tree-ssa-alias.h"
>  #include "gimple-expr.h"
> +#include "function.h"
> +#include "basic-block.h"
I thought I asked before, can these be moved into the .c files where
they're actually needed?

Otherwise it looks OK to me.

jeff
Martin Sebor Nov. 22, 2019, 1:09 a.m. UTC | #2
On 11/21/19 3:38 PM, Jeff Law wrote:
> On 11/21/19 10:11 AM, Martin Sebor wrote:
>> Attached is another revision of this enhancement, this one
>> incorporating Richard's request for a more efficient encoding
>> of the attributes to enable faster parsing.  There is just one
>> attribute called access, with the rest being arguments.  This
>> is transformed to access (mode-string) where the mode-string
>> is a STRING_CST describing the access mode (read/write/both)
>> and the two positional arguments for the function type.
>>
>> I have also removed the attributes from the built-in functions
>> since they're not used for anything yet, and added more argument
>> validation.
>>
>> To test this a little more extensively, I annotated a few Glibc
>> <unistd.h> functions with the new attribute and rebuilt it and
>> its test suite.  That exposed a problem with function pointers
>> not being handled correctly so I fixed that by letting
>> the access attribute apply to function pointers (and function
>> pointer types in general).
>>
>> When this is finalized, if there's time I'm still hoping to get
>> back to the parts of the patch that make use of the attribute
>> for -Wunused and -Wuninitialized that I removed on Jeff's request
>> for smaller, independent changes.
>>
>> In GCC 11 I'd like to look into tying this attribute in with
>> _FORTIFY_SOURCE.
>>
>> Martin
>>
>> gcc-83859.diff
>>
>> PR middle-end/83859 - attributes to associate pointer arguments and sizes
>>
>> gcc/ChangeLog:
>>
>> 	PR middle-end/83859
>> 	* attribs.h (struct attr_access): New.
>> 	* attribs.c (decl_attributes): Add an informational note.
>> 	* builtins.c (check_access): Make extern.  Consistently set no-warning
>> 	after issuing a warning.  Handle calls through function pointers.  Set
>> 	no-warning.
>> 	* builtins.h (check_access): Declare.
>> 	* calls.c (rdwr_access_hash): New type.
>> 	(rdwr_map): Same.
>> 	(init_attr_rdwr_indices): New function.
>> 	(maybe_warn_rdwr_sizes): Same.
>> 	(initialize_argument_information): Call init_attr_rdwr_indices.
>> 	Call maybe_warn_rdwr_sizes.
>> 	* doc/extend.texi (attribute access): Document new attribute.
>>
>> gcc/c-family/ChangeLog:
>>
>> 	PR middle-end/83859
>> 	* c-attribs.c (handle_access_attribute): New function.
>> 	(c_common_attribute_table): Add new attribute.
>> 	(get_argument_type): New function.
>> 	(append_access_attrs): New function.
>> 	(get_nonnull_operand): Rename...
>> 	(get_attribute_operand): ...to this.
>> 	* c-common.c (get_nonnull_operand): Rename...
>> 	(get_attribute_operand): ...to this.
>>
>> gcc/testsuite/ChangeLog:
>>
>> 	PR middle-end/83859
>> 	* c-c++-common/attr-nonstring-8.c: Adjust text of expected warning.
>> 	* gcc.dg/Wstringop-overflow-22.c: New test.
>> 	* gcc.dg/Wstringop-overflow-23.c: New test.
>> 	* gcc.dg/attr-access-read-only.c: New test.
>> 	* gcc.dg/attr-access-read-write.c: New test.
>> 	* gcc.dg/attr-access-read-write-2.c: New test.
>> 	* gcc.dg/attr-access-write-only.c: New test.
>>
> 
>> diff --git a/gcc/calls.c b/gcc/calls.c
>> index 62921351b11..15627abbd0d 100644
>> --- a/gcc/calls.c
>> +++ b/gcc/calls.c
>> @@ -52,6 +52,8 @@ along with GCC; see the file COPYING3.  If not see
>>   #include "tree-ssa-strlen.h"
>>   #include "intl.h"
>>   #include "stringpool.h"
>> +#include "hash-map.h"
>> +#include "hash-traits.h"
>>   #include "attribs.h"
>>   #include "builtins.h"
>>   #include "gimple-fold.h"
>> @@ -1258,6 +1260,9 @@ alloc_max_size (void)
>>   bool
>>   get_size_range (tree exp, tree range[2], bool allow_zero /* = false */)
>>   {
>> +  if (!exp)
>> +    return false;
>> +
>>     if (tree_fits_uhwi_p (exp))
>>       {
>>         /* EXP is a constant.  */
> This change isn't mentioned anywhere in the ChangeLog.  If its
> intentional, please mention it in the ChangeLog.  If it's not
> intentional, then drop it :-)

It's intentional.  I'll add it to the ChangeLog.

>> diff --git a/gcc/gimple.h b/gcc/gimple.h
>> index 5a190b1714d..10223386d04 100644
>> --- a/gcc/gimple.h
>> +++ b/gcc/gimple.h
>> @@ -24,6 +24,8 @@ along with GCC; see the file COPYING3.  If not see
>>   
>>   #include "tree-ssa-alias.h"
>>   #include "gimple-expr.h"
>> +#include "function.h"
>> +#include "basic-block.h"
> I thought I asked before, can these be moved into the .c files where
> they're actually needed?

With the latest changes the include directives are no loner needed.

(You did ask and I answered the question a couple of replies ago
when they still were necessary:
https://gcc.gnu.org/ml/gcc-patches/2019-11/msg01473.html)

> 
> Otherwise it looks OK to me.

Okay, I'll commit it tomorrow if Richard has no further suggestions
for changes.

Martin

> 
> jeff
>

Patch
diff mbox series

PR middle-end/83859 - attributes to associate pointer arguments and sizes

gcc/ChangeLog:

	PR middle-end/83859
	* attribs.h (struct attr_access): New.
	* attribs.c (decl_attributes): Add an informational note.
	* builtins.c (check_access): Make extern.  Consistently set no-warning
	after issuing a warning.  Handle calls through function pointers.  Set
	no-warning.
	* builtins.h (check_access): Declare.
	* calls.c (rdwr_access_hash): New type.
	(rdwr_map): Same.
	(init_attr_rdwr_indices): New function.
	(maybe_warn_rdwr_sizes): Same.
	(initialize_argument_information): Call init_attr_rdwr_indices.
	Call maybe_warn_rdwr_sizes.
	* doc/extend.texi (attribute access): Document new attribute.

gcc/c-family/ChangeLog:

	PR middle-end/83859
	* c-attribs.c (handle_access_attribute): New function.
	(c_common_attribute_table): Add new attribute.
	(get_argument_type): New function.
	(append_access_attrs): New function.
	(get_nonnull_operand): Rename...
	(get_attribute_operand): ...to this.
	* c-common.c (get_nonnull_operand): Rename...
	(get_attribute_operand): ...to this.

gcc/testsuite/ChangeLog:

	PR middle-end/83859
	* c-c++-common/attr-nonstring-8.c: Adjust text of expected warning.
	* gcc.dg/Wstringop-overflow-22.c: New test.
	* gcc.dg/Wstringop-overflow-23.c: New test.
	* gcc.dg/attr-access-read-only.c: New test.
	* gcc.dg/attr-access-read-write.c: New test.
	* gcc.dg/attr-access-read-write-2.c: New test.
	* gcc.dg/attr-access-write-only.c: New test.

diff --git a/gcc/attribs.c b/gcc/attribs.c
index b89be5834de..de34918919b 100644
--- a/gcc/attribs.c
+++ b/gcc/attribs.c
@@ -573,13 +573,23 @@  decl_attributes (tree *node, tree attributes, int flags,
 	    }
 	  continue;
 	}
-      else if (list_length (args) < spec->min_length
-	       || (spec->max_length >= 0
-		   && list_length (args) > spec->max_length))
+      else
 	{
-	  error ("wrong number of arguments specified for %qE attribute",
-		 name);
-	  continue;
+	  int nargs = list_length (args);
+	  if (nargs < spec->min_length
+	      || (spec->max_length >= 0
+		  && nargs > spec->max_length))
+	    {
+	      error ("wrong number of arguments specified for %qE attribute",
+		     name);
+	      if (spec->max_length < 0)
+		inform (input_location, "expected %i or more, found %i",
+			spec->min_length, nargs);
+	      else
+		inform (input_location, "expected between %i and %i, found %i",
+			spec->min_length, spec->max_length, nargs);
+	      continue;
+	    }
 	}
       gcc_assert (is_attribute_p (spec->name, name));
 
diff --git a/gcc/attribs.h b/gcc/attribs.h
index 23a7321e04a..9bc1600dfe3 100644
--- a/gcc/attribs.h
+++ b/gcc/attribs.h
@@ -218,4 +218,24 @@  lookup_attribute_by_prefix (const char *attr_name, tree list)
     }
 }
 
+/* Description of a function argument declared with attribute access.
+   Used as an "iterator" over all such arguments in a function declaration
+   or call.  */
+
+struct attr_access
+{
+  /* The attribute pointer argument.  */
+  tree ptr;
+  /* The size of the pointed-to object or NULL when not specified.  */
+  tree size;
+
+  /* The zero-based number of each of the formal function arguments.  */
+  unsigned ptrarg;
+  unsigned sizarg;
+
+  /* The access mode.  */
+  enum access_mode { read_only, write_only, read_write };
+  access_mode mode;
+};
+
 #endif // GCC_ATTRIBS_H
diff --git a/gcc/builtins.c b/gcc/builtins.c
index f94151bd84d..bd835c8fd3d 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -3299,7 +3299,7 @@  determine_block_size (tree len, rtx len_rtx,
    If the call is successfully verified as safe return true, otherwise
    return false.  */
 
-static bool
+bool
 check_access (tree exp, tree, tree, tree dstwrite,
 	      tree maxread, tree srcstr, tree dstsize)
 {
@@ -3395,16 +3395,26 @@  check_access (tree exp, tree, tree, tree dstwrite,
 
       bool warned;
       if (range[0] == range[1])
-	warned = warning_at (loc, opt,
-			     "%K%qD specified size %E "
-			     "exceeds maximum object size %E",
-			     exp, func, range[0], maxobjsize);
+	warned = (func
+		  ? warning_at (loc, opt,
+				"%K%qD specified size %E "
+				"exceeds maximum object size %E",
+				exp, func, range[0], maxobjsize)
+		  : warning_at (loc, opt,
+				"%Kspecified size %E "
+				"exceeds maximum object size %E",
+				exp, range[0], maxobjsize));
       else
-	warned = warning_at (loc, opt,
-			     "%K%qD specified size between %E and %E "
-			     "exceeds maximum object size %E",
-			     exp, func,
-			     range[0], range[1], maxobjsize);
+	warned = (func
+		  ? warning_at (loc, opt,
+				"%K%qD specified size between %E and %E "
+				"exceeds maximum object size %E",
+				exp, func,
+				range[0], range[1], maxobjsize)
+		  : warning_at (loc, opt,
+				"%Kspecified size between %E and %E "
+				"exceeds maximum object size %E",
+				exp, range[0], range[1], maxobjsize));
       if (warned)
 	TREE_NO_WARNING (exp) = true;
 
@@ -3433,37 +3443,69 @@  check_access (tree exp, tree, tree, tree dstwrite,
 	  location_t loc = tree_nonartificial_location (exp);
 	  loc = expansion_point_location_if_in_system_header (loc);
 
+	  bool warned = false;
 	  if (dstwrite == slen && at_least_one)
 	    {
 	      /* This is a call to strcpy with a destination of 0 size
 		 and a source of unknown length.  The call will write
 		 at least one byte past the end of the destination.  */
-	      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);
+	      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 if (tree_int_cst_equal (range[0], range[1]))
-	    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);
+	    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.  */
-	      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);
+	      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
-	    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);
+	    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));
+	  if (warned)
+	    TREE_NO_WARNING (exp) = true;
 
 	  /* Return error when an overflow has been detected.  */
 	  return false;
@@ -3486,21 +3528,36 @@  check_access (tree exp, tree, tree, tree dstwrite,
 	      if (TREE_NO_WARNING (exp))
 		return false;
 
+	      bool warned = false;
+
 	      /* Warn about crazy big sizes first since that's more
 		 likely to be meaningful than saying that the bound
 		 is greater than the object size if both are big.  */
 	      if (range[0] == range[1])
-		warning_at (loc, opt,
-			    "%K%qD specified bound %E "
-			    "exceeds maximum object size %E",
-			    exp, func,
-			    range[0], maxobjsize);
+		warned = (func
+			  ? warning_at (loc, opt,
+					"%K%qD specified bound %E "
+					"exceeds maximum object size %E",
+					exp, func, range[0], maxobjsize)
+			  : warning_at (loc, opt,
+					"%Kspecified bound %E "
+					"exceeds maximum object size %E",
+					exp, range[0], maxobjsize));
 	      else
-		warning_at (loc, opt,
-			    "%K%qD specified bound between %E and %E "
-			    "exceeds maximum object size %E",
-			    exp, func,
-			    range[0], range[1], maxobjsize);
+		warned = (func
+			  ? warning_at (loc, opt,
+					"%K%qD specified bound between "
+					"%E and %E exceeds maximum object "
+					"size %E",
+					exp, func,
+					range[0], range[1], maxobjsize)
+			  : warning_at (loc, opt,
+					"%Kspecified bound between "
+					"%E and %E exceeds maximum object "
+					"size %E",
+					exp, range[0], range[1], maxobjsize));
+	      if (warned)
+		TREE_NO_WARNING (exp) = true;
 
 	      return false;
 	    }
@@ -3510,18 +3567,34 @@  check_access (tree exp, tree, tree, tree dstwrite,
 	      if (TREE_NO_WARNING (exp))
 		return false;
 
+	      bool warned = false;
+
 	      if (tree_int_cst_equal (range[0], range[1]))
-		warning_at (loc, opt,
-			    "%K%qD specified bound %E "
-			    "exceeds destination size %E",
-			    exp, func,
-			    range[0], dstsize);
+		warned = (func
+			  ? warning_at (loc, opt,
+					"%K%qD specified bound %E "
+					"exceeds destination size %E",
+					exp, func,
+					range[0], dstsize)
+			  : warning_at (loc, opt,
+					"%Kspecified bound %E "
+					"exceeds destination size %E",
+					exp, range[0], dstsize));
 	      else
-		warning_at (loc, opt,
-			    "%K%qD specified bound between %E and %E "
-			    "exceeds destination size %E",
-			    exp, func,
-			    range[0], range[1], dstsize);
+		warned = (func
+			  ? warning_at (loc, opt,
+					"%K%qD specified bound between %E "
+					"and %E exceeds destination size %E",
+					exp, func,
+					range[0], range[1], dstsize)
+			  : warning_at (loc, opt,
+					"%Kspecified bound between %E "
+					"and %E exceeds destination size %E",
+					exp,
+					range[0], range[1], dstsize));
+	      if (warned)
+		TREE_NO_WARNING (exp) = true;
+
 	      return false;
 	    }
 	}
@@ -3536,26 +3609,46 @@  check_access (tree exp, tree, tree, tree dstwrite,
       if (TREE_NO_WARNING (exp))
 	return false;
 
+      bool warned = false;
       location_t loc = tree_nonartificial_location (exp);
+      loc = expansion_point_location_if_in_system_header (loc);
 
       if (tree_int_cst_equal (range[0], range[1]))
-	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);
+	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)
+		  : 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));
       else if (tree_int_cst_sign_bit (range[1]))
 	{
 	  /* Avoid printing the upper bound if it's invalid.  */
-	  warning_at (loc, opt,
-		      "%K%qD reading %E or more bytes from a region "
-		      "of size %E",
-		      exp, func, range[0], slen);
+	  warned = (func
+		    ? warning_at (loc, opt,
+				  "%K%qD reading %E or more bytes from a region "
+				  "of size %E",
+				  exp, func, range[0], slen)
+		    : warning_at (loc, opt,
+				  "%Kreading %E or more bytes from a region "
+				  "of size %E",
+				  exp, range[0], slen));
 	}
       else
-	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);
+	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)
+		  : warning_at (loc, opt,
+				"%Kreading between %E and %E bytes from "
+				"a region of size %E",
+				exp, range[0], range[1], slen));
+      if (warned)
+	TREE_NO_WARNING (exp) = true;
+
       return false;
     }
 
diff --git a/gcc/builtins.h b/gcc/builtins.h
index 1ad82e86963..2deb603059b 100644
--- a/gcc/builtins.h
+++ b/gcc/builtins.h
@@ -151,5 +151,7 @@  extern internal_fn replacement_internal_fn (gcall *);
 extern void warn_string_no_nul (location_t, const char *, tree, tree);
 extern tree unterminated_array (tree, tree * = NULL, bool * = NULL);
 extern bool builtin_with_linkage_p (tree);
+extern bool check_access (tree, tree, tree, tree, tree, tree, tree);
+
 
 #endif /* GCC_BUILTINS_H */
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index c62cebf7bfd..21073224426 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -125,6 +125,8 @@  static tree handle_nothrow_attribute (tree *, tree, tree, int, bool *);
 static tree handle_cleanup_attribute (tree *, tree, tree, int, bool *);
 static tree handle_warn_unused_result_attribute (tree *, tree, tree, int,
 						 bool *);
+static tree handle_access_attribute (tree *, tree, tree, int, bool *);
+
 static tree handle_sentinel_attribute (tree *, tree, tree, int, bool *);
 static tree handle_type_generic_attribute (tree *, tree, tree, int, bool *);
 static tree handle_alloc_size_attribute (tree *, tree, tree, int, bool *);
@@ -480,6 +482,8 @@  const struct attribute_spec c_common_attribute_table[] =
 			      handle_copy_attribute, NULL },
   { "noinit",		      0, 0, true,  false, false, false,
 			      handle_noinit_attribute, attr_noinit_exclusions },
+  { "access",		      1, 3, false, true, true, false,
+			      handle_access_attribute, NULL },
   { NULL,                     0, 0, false, false, false, false, NULL, NULL }
 };
 
@@ -511,7 +515,8 @@  attribute_takes_identifier_p (const_tree attr_id)
     return true;
   else if (!strcmp ("mode", spec->name)
 	   || !strcmp ("format", spec->name)
-	   || !strcmp ("cleanup", spec->name))
+	   || !strcmp ("cleanup", spec->name)
+	   || !strcmp ("access", spec->name))
     return true;
   else
     return targetm.attribute_takes_identifier_p (attr_id);
@@ -3798,6 +3803,387 @@  handle_nonstring_attribute (tree *node, tree name, tree ARG_UNUSED (args),
   return NULL_TREE;
 }
 
+/* Given a function type FUNCTYPE, returns the type of the parameter
+   ARGNO or null if ARGNO exceeds the number of parameters.  On failure
+   set *NARGS to the number of function parameters.  */
+
+static tree
+get_argument_type (tree functype, unsigned argno, unsigned *nargs)
+{
+  function_args_iterator iter;
+  function_args_iter_init (&iter, functype);
+
+  unsigned count = 0;
+
+  for ( ; iter.next; ++count, function_args_iter_next (&iter))
+    {
+      if (count + 1 == argno)
+	{
+	  tree argtype = function_args_iter_cond (&iter);
+	  if (VOID_TYPE_P (argtype))
+	    break;
+	  return argtype;
+	}
+    }
+
+  *nargs = count;
+  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.  */
+
+static tree
+append_access_attrs (tree t, tree attrs, const char *attrstr,
+		     char code, HOST_WIDE_INT idxs[2])
+{
+  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);
+
+  size_t newlen = n1 + n2;
+  char *newspec = attrspec;
+
+  if (tree acs = lookup_attribute ("access", attrs))
+    {
+      acs = TREE_VALUE (acs);
+      gcc_assert (TREE_CODE (acs) == STRING_CST);
+
+      /* 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.  */
+
+      const char *posstr = attrspec + 1;
+      const char *str = TREE_STRING_POINTER (acs);
+      const char *pos = str;
+      for ( ; ; pos += n1)
+	{
+	  pos = strstr (pos, posstr);
+	  if (!pos)
+	    break;
+
+	  if (ISDIGIT (pos[-1]) || ISDIGIT (pos[n1 -1]))
+	    continue;
+
+	  /* Found a matching positional argument.  */
+	  if (*attrspec != pos[-1])
+	    {
+	      /* Mismatch in access mode.  */
+	      if (warning (OPT_Wattributes,
+			   "attribute %qs mismatch with mode %qs",
+			   attrstr,
+			   (pos[-1] == 'r'
+			    ? "read_only"
+			    : (pos[-1] == 'w' ? "write_only" : "read_write")))
+		  && DECL_P (t))
+		inform (DECL_SOURCE_LOCATION (t),
+			"previous declaration here");
+	      return NULL_TREE;
+	    }
+
+	  if ((n2 && pos[n1 - 1] != ','))
+	    {
+	      /* Mismatch in the presence of the size argument.  */
+	      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 (!n2 && pos[n1 - 1] == ',')
+	    {
+	      /* Mismatch in the presence of the size argument.  */
+	      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;
+	    }
+
+	  if (n2 && strncmp (attrstr + n1 + 1, pos + n1, n2))
+	    {
+	      /* Mismatch in the value of the size argument.  */
+	      if (warning (OPT_Wattributes,
+			   "attribute %qs mismatch positional argument "
+			   "values %i and %i",
+			   attrstr, atoi (attrstr + n1 + 1), atoi (pos + n1))
+		  && DECL_P (t))
+		inform (DECL_SOURCE_LOCATION (t),
+			"previous declaration here");
+	      return NULL_TREE;
+	    }
+
+	  /* Avoid adding the same attribute specification.  */
+	  return NULL_TREE;
+	}
+
+      /* Connect the two substrings formatted above into a single one.  */
+      if (idxs[1])
+	attrspec[n1] = ',';
+
+      size_t len = strlen (str);
+      newspec = (char *) xmalloc (newlen + len + 1);
+      strcpy (newspec, str);
+      strcpy (newspec + len, attrspec);
+      newlen += len;
+    }
+  else if (idxs[1])
+    /* Connect the two substrings formatted above into a single one.  */
+    attrspec[n1] = ',';
+
+  return build_string (newlen + 1, newspec);
+}
+
+/* Handle the access attribute (read_only, write_only, and read_write).  */
+
+static tree
+handle_access_attribute (tree *node, tree name, tree args,
+			 int ARG_UNUSED (flags), bool *no_add_attrs)
+{
+  tree type = *node;
+  tree attrs = TYPE_ATTRIBUTES (type);
+
+  *no_add_attrs = true;
+
+  /* Verify a full prototype is provided so that the argument types
+     can be validated.  Avoid diagnosing type-generic built-ins since
+     those have no prototype.  */
+  if (!args
+      && !prototype_p (type)
+      && (!attrs || !lookup_attribute ("type generic", attrs)))
+    {
+      error ("attribute %qE without arguments on a non-prototype", name);
+      return NULL_TREE;
+    }
+
+  /* Set to true when the access mode has the form of a function call
+     as in 'attribute (read_only (1, 2))'.  That's an easy mistake to
+     make and so worth a special diagnostic.  */
+  bool funcall = false;
+  tree access_mode = TREE_VALUE (args);
+  if (TREE_CODE (access_mode) == CALL_EXPR)
+    {
+      access_mode = CALL_EXPR_FN (access_mode);
+      if (TREE_CODE (access_mode) != ADDR_EXPR)
+	{
+	  error ("attribute %qE invalid mode", name);
+	  return NULL_TREE;
+	}
+      access_mode = TREE_OPERAND (access_mode, 0);
+      access_mode = DECL_NAME (access_mode);
+      funcall = true;
+    }
+
+  const char* const access_str = IDENTIFIER_POINTER (access_mode);
+  const char *ps = access_str;
+  if (ps[0] == '_' && ps[1] == '_')
+    {
+      size_t len = strlen (ps);
+      if (ps[len - 1] == '_' && ps[len - 2] == '_')
+	ps += 2;
+    }
+
+  const bool read_only = strncmp (ps, "read_only", 9) == 0;
+  const bool write_only = strncmp (ps, "write_only", 9) == 0;
+  if (!read_only && !write_only && strncmp (ps, "read_write", 9))
+    {
+      error ("attribute %qE invalid mode %qs; expected one of "
+	     "%qs, %qs, or %qs", name, access_str,
+	     "read_only", "read_write", "write_only");
+      return NULL_TREE;
+    }
+
+  if (funcall)
+    {
+      error ("attribute %qE unexpected %<(%> after mode %qs; expected "
+	     "a positional argument or %<)%>",
+	     name, access_str);
+      return NULL_TREE;
+    }
+
+  args = TREE_CHAIN (args);
+  if (!args)
+    {
+      /* The first positional argument is required.  It may be worth
+	 dropping the requirement at some point and having read_only
+	 apply to all const-qualified pointers and read_write or
+	 write_only to the rest.  */
+      error ("attribute %<%E(%s)%> missing an argument",
+	     name, access_str);
+      return NULL_TREE;
+    }
+
+  /* One or more positional arguments have been specified.  Validate
+     them.  */
+  tree idxnodes[2] = { NULL_TREE, NULL_TREE };
+  tree argtypes[2] = { NULL_TREE, NULL_TREE };
+  /* 1-based attribute positional arguments or zero if not specified.
+     Invalid negative or excessive values are also stored but used
+     only in diagnostics.  */
+  HOST_WIDE_INT idxs[2] = { 0, 0 };
+
+  /* Number of function formal arguments (used in diagnostics).  */
+  unsigned nfuncargs = 0;
+  /* Number of (optional) attribute positional arguments.  */
+  unsigned nattrargs = 0;
+
+  for (unsigned i = 0; i != 2; ++i, args = TREE_CHAIN (args), ++nattrargs)
+    {
+      if (!args)
+	break;
+
+      idxnodes[i] = TREE_VALUE (args);
+
+      if (TREE_CODE (idxnodes[i]) != IDENTIFIER_NODE
+	  && TREE_CODE (idxnodes[i]) != FUNCTION_DECL)
+	idxnodes[i] = default_conversion (idxnodes[i]);
+
+      if (tree_fits_shwi_p (idxnodes[i]))
+	{
+	  idxs[i] = tree_to_shwi (idxnodes[i]);
+	  argtypes[i] = get_argument_type (type, idxs[i], &nfuncargs);
+	}
+    }
+
+  if ((nattrargs == 1 && !idxs[0])
+      || (nattrargs == 2 && (!idxs[0] || !idxs[1])))
+    {
+      if (idxnodes[1])
+	error ("attribute %<%E(%s, %E, %E)%> invalid positional argument %i",
+	       name, access_str, idxnodes[0], idxnodes[1], idxs[0] ? 2 : 1);
+      else
+	error ("attribute %<%E(%s, %E)%> invalid positional argument %i",
+	       name, access_str, idxnodes[0], idxs[0] ? 2 : 1);
+      return NULL_TREE;
+    }
+
+  /* Format the attribute specification to include in diagnostics.  */
+  char attrstr[80];
+  if (idxnodes[1])
+    snprintf (attrstr, sizeof attrstr, "%s(%s, %lli, %lli)",
+	      IDENTIFIER_POINTER (name), access_str,
+	      (long long) idxs[0], (long long) idxs[1]);
+  else if (idxnodes[0])
+    snprintf (attrstr, sizeof attrstr, "%s(%s, %lli)",
+	      IDENTIFIER_POINTER (name), access_str,
+	      (long long) idxs[0]);
+  else
+    snprintf (attrstr, sizeof attrstr, "%s(%s)",
+	      IDENTIFIER_POINTER (name), access_str);
+
+  /* Verify the positional argument values are in range.  */
+  if (!argtypes[0] || (idxnodes[1] && !argtypes[1]))
+    {
+      if (idxnodes[0])
+	{
+	  if (idxs[0] < 0 || idxs[1] < 0)
+	    error ("attribute %qs positional argument %i invalid value %wi",
+		   attrstr, idxs[0] < 0 ? 1 : 2,
+		   idxs[0] < 0 ? idxs[0] : idxs[1]);
+	  else
+	    error ("attribute %qs positional argument %i value %wi exceeds "
+		   "number of function arguments %u",
+		   attrstr, idxs[0] ? 1 : 2,
+		   idxs[0] ? idxs[0] : idxs[1],
+		   nfuncargs);
+	}
+      else
+	error ("attribute %qs invalid positional argument", attrstr);
+
+      return NULL_TREE;
+    }
+
+  if (!POINTER_TYPE_P (argtypes[0]))
+    {
+      /* The first argument must have a pointer or reference type.  */
+      error ("attribute %qs positional argument 1 references "
+	     "non-pointer argument type %qT",
+	     attrstr, argtypes[0]);
+      return NULL_TREE;
+    }
+
+  {
+    /* Pointers to functions are not allowed.  */
+    tree ptrtype = TREE_TYPE (argtypes[0]);
+    if (FUNC_OR_METHOD_TYPE_P (ptrtype))
+      {
+	error ("attribute %qs positional argument 1 references "
+	       "argument of function type %qT",
+	       attrstr, ptrtype);
+	return NULL_TREE;
+      }
+  }
+
+  if (!read_only)
+    {
+      /* A read_write and write_only modes must reference non-const
+	 arguments.  */
+      if (TYPE_READONLY (TREE_TYPE (argtypes[0])))
+	{
+	  error ("attribute %qs positional argument 1 references "
+		 "%qs-qualified argument type %qT",
+		 attrstr, "const", argtypes[0]);
+	  return NULL_TREE;
+	}
+    }
+  else if (!TYPE_READONLY (TREE_TYPE (argtypes[0])))
+    {
+      /* A read_only mode should ideally reference const-qualified
+	 arguments but it's not diagnosed error if one doesn't.
+	 This makes it possible to annotate legacy, const-incorrect
+	 APIs.  It might be worth a diagnostic along the lines of
+	 -Wsuggest-const.  */
+      ;
+    }
+
+  if (argtypes[1] && !INTEGRAL_TYPE_P (argtypes[1]))
+    {
+      error ("attribute %qs positional argument 2 references "
+	     "non-integer argument type %qT",
+	     attrstr, argtypes[1]);
+      return NULL_TREE;
+    }
+
+  /* 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' : 'x';
+  tree new_attrs = append_access_attrs (node[0], attrs, attrstr, code, idxs);
+  if (!new_attrs)
+    return NULL_TREE;
+
+  /* Replace any existing access attribute specification with
+     the concatenation above.  */
+  attrs = remove_attribute (IDENTIFIER_POINTER (name), attrs);
+  new_attrs = tree_cons (name, new_attrs, attrs);
+
+  if (node[1])
+    {
+      /* Repeat for the previously declared type.  */
+      attrs = TYPE_ATTRIBUTES (TREE_TYPE (node[1]));
+      tree new_attrs = append_access_attrs (node[1], attrs, attrstr, code, idxs);
+      if (!new_attrs)
+	return NULL_TREE;
+
+      attrs = remove_attribute (IDENTIFIER_POINTER (name), attrs);
+      new_attrs = tree_cons (name, new_attrs, attrs);
+      TYPE_ATTRIBUTES (TREE_TYPE (node[1])) = new_attrs;
+    }
+
+  TYPE_ATTRIBUTES (*node) = new_attrs;
+  return NULL_TREE;
+}
+
 /* Handle a "nothrow" attribute; arguments as in
    struct attribute_spec.handler.  */
 
diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c
index 48811994f38..4d143adb601 100644
--- a/gcc/c-family/c-common.c
+++ b/gcc/c-family/c-common.c
@@ -5483,7 +5483,7 @@  nonnull_check_p (tree args, unsigned HOST_WIDE_INT param_num)
 
   for (; args; args = TREE_CHAIN (args))
     {
-      bool found = get_nonnull_operand (TREE_VALUE (args), &arg_num);
+      bool found = get_attribute_operand (TREE_VALUE (args), &arg_num);
 
       gcc_assert (found);
 
@@ -5518,11 +5518,11 @@  check_nonnull_arg (void *ctx, tree param, unsigned HOST_WIDE_INT param_num)
     }
 }
 
-/* Helper for nonnull attribute handling; fetch the operand number
-   from the attribute argument list.  */
+/* Helper for attribute handling; fetch the operand number from
+   the attribute argument list.  */
 
 bool
-get_nonnull_operand (tree arg_num_expr, unsigned HOST_WIDE_INT *valp)
+get_attribute_operand (tree arg_num_expr, unsigned HOST_WIDE_INT *valp)
 {
   /* Verify the arg number is a small constant.  */
   if (tree_fits_uhwi_p (arg_num_expr))
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index 80a8c9f4543..285f0f11dad 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -880,7 +880,7 @@  extern bool pointer_to_zero_sized_aggr_p (tree);
 extern bool bool_promoted_to_int_p (tree);
 extern tree fold_for_warn (tree);
 extern tree c_common_get_narrower (tree, int *);
-extern bool get_nonnull_operand (tree, unsigned HOST_WIDE_INT *);
+extern bool get_attribute_operand (tree, unsigned HOST_WIDE_INT *);
 
 #define c_sizeof(LOC, T)  c_sizeof_or_alignof_type (LOC, T, true, false, 1)
 #define c_alignof(LOC, T) c_sizeof_or_alignof_type (LOC, T, false, false, 1)
diff --git a/gcc/calls.c b/gcc/calls.c
index 62921351b11..15627abbd0d 100644
--- a/gcc/calls.c
+++ b/gcc/calls.c
@@ -52,6 +52,8 @@  along with GCC; see the file COPYING3.  If not see
 #include "tree-ssa-strlen.h"
 #include "intl.h"
 #include "stringpool.h"
+#include "hash-map.h"
+#include "hash-traits.h"
 #include "attribs.h"
 #include "builtins.h"
 #include "gimple-fold.h"
@@ -1258,6 +1260,9 @@  alloc_max_size (void)
 bool
 get_size_range (tree exp, tree range[2], bool allow_zero /* = false */)
 {
+  if (!exp)
+    return false;
+
   if (tree_fits_uhwi_p (exp))
     {
       /* EXP is a constant.  */
@@ -1870,6 +1875,309 @@  maybe_complain_about_tail_call (tree call_expr, const char *reason)
   error_at (EXPR_LOCATION (call_expr), "cannot tail-call: %s", reason);
 }
 
+/* Used to define rdwr_map below.  */
+struct rdwr_access_hash: int_hash<int, -1> { };
+
+/* A mapping between argument number corresponding to attribute access
+   mode (read_only, write_only, or read_write) and operands.  */
+typedef hash_map<rdwr_access_hash, attr_access> rdwr_map;
+
+/* Initialize a mapping for a call to function FNDECL declared with
+   attribute access.  Each attribute poisitional operand inserts one
+   entry into the mapping with the operand number as the key.  */
+
+static void
+init_attr_rdwr_indices (rdwr_map *rwm, tree fntype)
+{
+  if (!fntype)
+    return;
+
+  tree access = TYPE_ATTRIBUTES (fntype);
+  /* If the function's type has no attributes there's nothing to do.  */
+  if (!access)
+    return;
+
+  access = lookup_attribute ("access", access);
+  if (!access)
+    return;
+
+  tree mode = TREE_VALUE (access);
+  gcc_assert (TREE_CODE (mode) == STRING_CST);
+  const char *modestr = TREE_STRING_POINTER (mode);
+  for (const char *m = modestr; *m; )
+    {
+      attr_access acc = { };
+
+      switch (*m)
+	{
+	case 'r': acc.mode = acc.read_only; break;
+	case 'w': acc.mode = acc.write_only; break;
+	default: acc.mode = acc.read_write; break;
+	}
+
+      char *end;
+      acc.ptrarg = strtoul (++m, &end, 10);
+      m = end;
+      if (*m == ',')
+	{
+	  acc.sizarg = strtoul (++m, &end, 10);
+	  m = end;
+	}
+      else
+	acc.sizarg = UINT_MAX;
+
+      acc.ptr = NULL_TREE;
+      acc.size = NULL_TREE;
+
+      /* 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);
+    }
+}
+
+/* Returns the type of the argument ARGNO to function with type FNTYPE
+   or null when the typoe cannot be determined or no such argument exists.  */
+
+static tree
+fntype_argno_type (tree fntype, unsigned argno)
+{
+  if (!prototype_p (fntype))
+    return NULL_TREE;
+
+  tree argtype;
+  function_args_iterator it;
+  FOREACH_FUNCTION_ARGS (fntype, argtype, it)
+    if (argno-- == 0)
+      return argtype;
+
+  return NULL_TREE;
+}
+
+/* Helper to append the "rdwr" attribute 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 *atname
+    = (access.second.mode == attr_access::read_only
+       ? "read_only"
+       : (access.second.mode == attr_access::write_only
+	  ? "write_only" : "read_write"));
+
+  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);
+}
+
+/* Iterate over attribute access read-only, read-write, and write-only
+   arguments and diagnose past-the-end accesses and related problems
+   in the function call EXP.  */
+
+static void
+maybe_warn_rdwr_sizes (rdwr_map *rwm, tree exp)
+{
+  tree fndecl = NULL_TREE;
+  tree fntype = NULL_TREE;
+  if (tree fnaddr = CALL_EXPR_FN (exp))
+    {
+      if (TREE_CODE (fnaddr) == ADDR_EXPR)
+	{
+	  fndecl = TREE_OPERAND (fnaddr, 0);
+	  fntype = TREE_TYPE (fndecl);
+	}
+      else
+	fntype = TREE_TYPE (TREE_TYPE (fnaddr));
+    }
+
+  if (!fntype)
+    return;
+
+  /* A string describing the attributes that the warnings issued by this
+     function apply to.  Used to print one informational note per function
+     call, rather than one per warning.  That reduces clutter.  */
+  char attrstr[80];
+  attrstr[0] = 0;
+
+  for (rdwr_map::iterator it = rwm->begin (); it != rwm->end (); ++it)
+    {
+      std::pair<int, attr_access> access = *it;
+
+      /* Get the function call arguments corresponding to the attribute's
+	 positional arguments.  When both arguments have been specified
+	 there will be two entries in *RWM, one for each.  They are
+	 cross-referenced by their respective argument numbers in
+	 ACCESS.PTRARG and ACCESS.SIZARG.  */
+      const int ptridx = access.second.ptrarg;
+      const int sizidx = access.second.sizarg;
+
+      gcc_assert (ptridx != -1);
+      gcc_assert (access.first == ptridx || access.first == sizidx);
+
+      /* The pointer is set to null for the entry corresponding to
+	 the size argument.  Skip it.  It's handled when the entry
+	 corresponding to the pointer argument comes up.  */
+      if (!access.second.ptr)
+	continue;
+
+      tree argtype = fntype_argno_type (fntype, ptridx);
+      argtype = TREE_TYPE (argtype);
+
+      tree 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;
+	}
+      else
+	size = rwm->get (sizidx)->size;
+
+      tree ptr = access.second.ptr;
+      tree sizrng[2] = { size_zero_node, build_all_ones_cst (sizetype) };
+      if (get_size_range (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);
+	  else
+	    warned = warning_at (loc, OPT_Wstringop_overflow_,
+				 "%Kargument %i range [%E, %E] is negative",
+				 exp, sizidx + 1, sizrng[0], sizrng[1]);
+	  if (warned)
+	    {
+	      append_attrname (access, attrstr, sizeof attrstr);
+	      /* Avoid warning again for the same attribute.  */
+	      continue;
+	    }
+	}
+
+      if (tree_int_cst_sgn (sizrng[0]) >= 0)
+	{
+	  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;
+	      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);
+		  }
+	    }
+	}
+      else
+	size = NULL_TREE;
+
+      if (sizidx >= 0
+	  && integer_zerop (ptr)
+	  && 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.  */
+	  bool warned = false;
+	  location_t loc = 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 (warned)
+	    {
+	      append_attrname (access, attrstr, sizeof attrstr);
+	      /* Avoid warning again for the same attribute.  */
+	      continue;
+	    }
+	}
+
+      tree objsize = compute_objsize (ptr, 0);
+
+      tree srcsize;
+      if (access.second.mode == attr_access::write_only)
+	{
+	  /* For a write-only argument there is no source.  */
+	  srcsize = NULL_TREE;
+	}
+      else
+	{
+	  /* For read-only and read-write attributes also set the source
+	     size.  */
+	  srcsize = objsize;
+	  if (access.second.mode == attr_access::read_only)
+	    {
+	      /* 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.  */
+	      objsize = NULL_TREE;
+	    }
+	}
+
+      /* Clear the no-warning bit in case it was set 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);
+
+      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);
+    }
+
+  if (!*attrstr)
+    return;
+
+  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);
+
+  /* Set the bit in case if was cleared and not set above.  */
+  TREE_NO_WARNING (exp) = true;
+}
+
 /* Fill in ARGS_SIZE and ARGS array based on the parameters found in
    CALL_EXPR EXP.
 
@@ -1986,6 +2294,11 @@  initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
   /* Array for up to the two attribute alloc_size arguments.  */
   tree alloc_args[] = { NULL_TREE, NULL_TREE };
 
+  /* Map of attribute read_only, write_only, or read_write specifications
+     for function arguments.  */
+  rdwr_map rdwr_idx;
+  init_attr_rdwr_indices (&rdwr_idx, fntype);
+
   /* I counts args in order (to be) pushed; ARGPOS counts in order written.  */
   for (argpos = 0; argpos < num_actuals; i--, argpos++)
     {
@@ -2226,6 +2539,22 @@  initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
 	alloc_args[0] = args[i].tree_value;
       else if (argpos == alloc_idx[1])
 	alloc_args[1] = args[i].tree_value;
+
+      /* Save the actual argument that corresponds to the access attribute
+	 operand for later processing.  */
+      if (attr_access *access = rdwr_idx.get (argpos))
+	{
+	  if (POINTER_TYPE_P (type))
+	    {
+	      access->ptr = args[i].tree_value;
+	      gcc_assert (access->size == NULL_TREE);
+	    }
+	  else
+	    {
+	      access->size = args[i].tree_value;
+	      gcc_assert (access->ptr == NULL_TREE);
+	    }
+	}
     }
 
   if (alloc_args[0])
@@ -2238,6 +2567,9 @@  initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
   /* Detect passing non-string arguments to functions expecting
      nul-terminated strings.  */
   maybe_warn_nonstring_arg (fndecl, exp);
+
+  /* Check read_only, write_only, and read_write arguments.  */
+  maybe_warn_rdwr_sizes (&rdwr_idx, exp);
 }
 
 /* Update ARGS_SIZE to contain the total size for the argument block.
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 1c8ae0d5cd3..85573f7327d 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -2484,6 +2484,77 @@  The following attributes are supported on most targets.
 @table @code
 @c Keep this table alphabetized by attribute name.  Treat _ as space.
 
+@item access
+@itemx access (@var{access-mode}, @var{ref-index})
+@itemx access (@var{access-mode}, @var{ref-index}, @var{size-index})
+
+The @code{access} attribute enables the detection of invalid or unsafe
+accesses by functions to which they apply to or their callers, as well
+as wite-only accesses to objects that are never read from.  Such accesses
+may be diagnosed by warnings such as @option{-Wstringop-overflow},
+@option{-Wunnitialized}, @option{-Wunused}, and others.
+
+The @code{access} attribute specifies that a function to whose by-reference
+arguments the attribute applies accesses the referenced object according to
+@var{access-mode}.  The @var{access-mode} argument is required and must be
+one of three names: @code{read_only}, @code{read_write}, or @code{write_only}.
+The remaining two are positional arguments.
+
+The required @var{ref-index} positional argument  denotes a function
+argument of pointer (or in C++, refeference) type that is subject to
+the access.  The same pointer argument can be referenced by at most one
+distinct @code{access} attribute.
+
+The optional @var{size-index} positional argument denotes a function
+argument of integer type that specifies the maximum size of the access.
+The size is the number of elements of the type refefenced by @var{ref-index},
+or the number of bytes when the pointer type is @code{void*}.  When no
+@var{size-index} argument is specified, the pointer argument must be either
+null or point to a space that is suitably aligned and large for at least one
+object of the referenced type (this implies that a past-the-end pointer is
+not a valid argument).  The actual size of the access may be less but it
+must not be more.
+
+The @code{read_only} access mode specifies that the pointer to which it
+applies is used to read the referenced object but not write to it.  Unless
+the argument specifying the size of the access denoted by @var{size-index}
+is zero, the referenced object must be initialized.  The mode implies
+a stronger guarantee than the @code{const} qualifier which, when cast away
+from a pointer, does not prevent a function from modifying the pointed-to
+object.  Examples of the use of the @code{read_only} access mode is
+the argument to the @code{puts} function, or the second and third arguments
+to the @code{memcpy} function.
+
+@smallexample
+__attribute__ ((access (read_only))) int puts (const char*);
+__attribute__ ((access (read_only, 1, 2))) void* memcpy (void*, const void*, size_t);
+@end smallexample
+
+The @code{read_write} access mode applies to arguments of pointer types
+without the @code{const} qualifier.  It specifies that the pointer to which
+it applies is used to both read and write the referenced object.  Unless
+the argument specifying the size of the access denoted by @var{size-index}
+is zero, the object refrenced by the pointer must be initialized.  An example
+of the use of the @code{read_write} access mode is the first argument to
+the @code{strcat} function.
+
+@smallexample
+__attribute__ ((access (read_write, 1), access (read_only, 2))) char* strcat (char*, const char*);
+@end smallexample
+
+The @code{write_only} access mode applies to arguments of pointer types
+without the @code{const} qualifier.  It specifies that the pointer to which
+it applies is used to write to the referenced object but not read from it.
+The object refrenced by the pointer need not be initialized.  An example
+of the use of the @code{write_only} access mode is the first argument to
+the @code{strcpy} function, or the first two arguments to the @code{fgets}
+function.
+
+@smallexample
+__attribute__ ((access (write_only, 1), access (read_only, 2))) char* strcpy (char*, const char*);
+__attribute__ ((access (write_only, 1, 2), access (read_write, 3))) int fgets (char*, int, FILE*);
+@end smallexample
+
 @item alias ("@var{target}")
 @cindex @code{alias} function attribute
 The @code{alias} attribute causes the declaration to be emitted as an
@@ -3849,7 +3920,6 @@  performing a link with relocatable output (ie: @code{ld -r}) on them.
 At present, a declaration to which @code{weakref} is attached can
 only be @code{static}.
 
-
 @end table
 
 @c This is the end of the target-independent attribute table
diff --git a/gcc/gimple.h b/gcc/gimple.h
index 5a190b1714d..10223386d04 100644
--- a/gcc/gimple.h
+++ b/gcc/gimple.h
@@ -24,6 +24,8 @@  along with GCC; see the file COPYING3.  If not see
 
 #include "tree-ssa-alias.h"
 #include "gimple-expr.h"
+#include "function.h"
+#include "basic-block.h"
 
 typedef gimple *gimple_seq_node;
 
diff --git a/gcc/testsuite/c-c++-common/attr-nonstring-8.c b/gcc/testsuite/c-c++-common/attr-nonstring-8.c
index 36ab2a66180..fbae8bae5f7 100644
--- a/gcc/testsuite/c-c++-common/attr-nonstring-8.c
+++ b/gcc/testsuite/c-c++-common/attr-nonstring-8.c
@@ -57,8 +57,8 @@  void test_strncat_nonstring_cst (char *d)
   T (strncat (nd3, ns3, 1));
   T (strncat (nd3, ns3, 2));
   T (strncat (nd3, ns3, 3));     /* { dg-warning "specified bound 3 equals destination size" } */
-  T (strncat (nd3, ns3, 4));     /* { dg-warning "argument 2 declared attribute .nonstring. is smaller than the specified bound 4" } */
-  /* { dg-warning "specified bound 4 exceeds destination size 3" "" { target *-*-* } .-1 } */
+  /* Either of the two warnings below is fine.  */
+  T (strncat (nd3, ns3, 4));     /* { dg-warning "argument 2 declared attribute .nonstring. is smaller than the specified bound 4|specified bound 4 exceeds destination size 3" } */
 
   T (strncat (d, pns, sizeof pns));   /* { dg-warning "argument to .sizeof. in .\[^\n\r\]*strncat\[^\n\r\]*. call is the same expression as the source" } */
 }
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-22.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-22.c
new file mode 100644
index 00000000000..8a490d7b7ef
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-22.c
@@ -0,0 +1,204 @@ 
+/* PR middle-end/83859 - attribute to establish relation between parameters
+   for buffer and its size
+   Test to verify that -Wstringop-overflow warnings are issued even with
+   no optimization for calls to user-defined functions with attribute
+   access and with constant out-of-bounds arguments.
+   { dg-do compile }
+   { dg-options "-O0 -Wall" } */
+
+#define INT_MAX   __INT_MAX__
+#define INT_MIN   (-INT_MAX - 1)
+
+#define rdonly       __attribute__ ((access (read_only)))
+#define RDONLY(...)  __attribute__ ((access (read_only, __VA_ARGS__)))
+#define WRONLY(...)  __attribute__ ((access (write_only, __VA_ARGS__)))
+#define RDWR(...)    __attribute__ ((access (read_write, __VA_ARGS__)))
+
+typedef __INT32_TYPE__ int32_t;
+
+extern const char s1[1], s2[2], s3[3];
+extern char d1[1], d2[2], d3[3];
+
+/* Exercise that null pointers are allowed in functions declared with
+   the attribute without a size operand.  */
+
+RDONLY (1) void
+rd1_int (const int*);       // { dg-message "in a call to function 'rd1_int' declared with attribute 'read_only \\\(1\\\)'" }
+
+void test_rd1_int (void)
+{
+  rd1_int (0);
+
+  int32_t i = 0;
+  rd1_int (&i);
+
+  rd1_int ((int32_t*)s1);   // { dg-warning "reading 4 bytes from a region of size 1" }
+}
+
+/* Exercise null pointer detection in functions declared with
+   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\\\)" }
+
+void test_rd2_1 (void)
+{
+  rd2_1 (0, 0);
+  rd2_1 (1, "");
+  rd2_1 (1, 0);             // { dg-warning "argument 2 is null but the corresponding size argument 1 value is 1" }
+}
+
+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\\\)" }
+
+void test_wr3_1 (void)
+{
+  wr3_1 (0, 0, 0);
+  wr3_1 (1, 0, d1);
+  wr3_1 (2, 1, 0);          // { dg-warning "argument 3 is null but the corresponding size argument 1 value is 2" }
+}
+
+
+/* Exercise pointer to an incomplete type other than void.  */
+
+struct Incomplete;
+extern struct Incomplete inc;
+
+RDONLY (1) void
+rd_inc (const struct Incomplete*);
+
+void test_rd_inc (const struct Incomplete *pinc)
+{
+  rd_inc (0);
+
+  rd_inc (pinc);
+  rd_inc ((const struct Incomplete*)s1);
+
+  rd_inc ((const struct Incomplete*)&s1[1]);
+  // { dg-warning "'rd_inc' reading 1 byte from a region of size 0" "past-the-end pointer" { target *-*-* } .-1 }
+}
+
+RDONLY (1, 2) void
+rd1_2_inc (const struct Incomplete*, unsigned);
+
+void test_rd1_2_inc (const struct Incomplete *pinc)
+{
+  rd1_2_inc (0, 0);
+  rd1_2_inc (0, 1);         // { dg-warning "argument 1 is null but the corresponding size argument 2 value is 1" }
+
+  rd1_2_inc (pinc, 1);
+  rd1_2_inc (&inc, 1);
+
+  rd1_2_inc (pinc, 123);
+  rd1_2_inc (&inc, 456);
+
+  rd1_2_inc ((const struct Incomplete*)s3, 4);
+  // { dg-warning "'rd1_2_inc' reading 4 bytes from a region of size 3" "small buffer cast to incomplete" { target *-*-* } .-1 }
+}
+
+
+/* Verify the handling of two attributes sharing the same size operand .  */
+
+RDONLY (1, 3) WRONLY (2, 3) void
+rd1_3_wr2_3 (const void*, void*, int);
+
+void test_rd1_3_wr2_3 (void)
+{
+  rd1_3_wr2_3 (s1, d1, 0);
+  rd1_3_wr2_3 (s1, d1, 1);
+
+  rd1_3_wr2_3 (s1, d1, 2);
+  // { dg-warning "'rd1_3_wr2_3' reading 2 bytes from a region of size 1" "read" { target *-*-* } .-1 }
+  // { dg-warning "'rd1_3_wr2_3' writing 2 bytes into a region of size 1" "write" { target *-*-* } .-2 }
+
+  rd1_3_wr2_3 (s1, d2, 2);
+  // { dg-warning "'rd1_3_wr2_3' reading 2 bytes from a region of size 1" "read" { target *-*-* } .-1 }
+
+  rd1_3_wr2_3 (s2, d1, 2);
+  // { dg-warning "'rd1_3_wr2_3' writing 2 bytes into a region of size 1" "write" { target *-*-* } .-1 }
+}
+
+
+/* Verify the handling of multiple attributes of the same kind with
+   out-of-order operands.  */
+
+RDONLY (1, 6) RDONLY (2, 5) RDONLY (3, 4) void
+rd1_6_2_5_3_4 (const void *s1, const void *s2, const void *s3,
+	       int         n3, int         n2, int         n1);
+
+void test_rd1_6_2_5_3_4 (void)
+{
+  rd1_6_2_5_3_4 (s1, s2, s3, 4, 2, 1);   // { dg-warning "reading 4 bytes from a region of size 3" }
+  rd1_6_2_5_3_4 (s1, s2, s3, 3, 5, 1);   // { dg-warning "reading 5 bytes from a region of size 2" }
+  rd1_6_2_5_3_4 (s1, s2, s3, 3, 2, 6);   // { dg-warning "reading 6 bytes from a region of size 1" }
+}
+
+
+/* Verify the handling of multiple attributes of different kinds with
+   out-of-order operands.  */
+
+RDONLY (1, 6) WRONLY (2, 5) RDONLY (3, 4) void
+rd1_6_wr2_5_rd3_4 (const void *s1, void *d2, const void *s3,
+		   int         n3, int   n2, int         n1);
+
+void test_rd1_6_wr2_5_rd3_4 (void)
+{
+  rd1_6_wr2_5_rd3_4 (s1, d2, s3, 7, 2, 1);   // { dg-warning "reading 7 bytes from a region of size 3" }
+  rd1_6_wr2_5_rd3_4 (s1, d2, s3, 3, 8, 1);   // { dg-warning "writing 8 bytes into a region of size 2" }
+  rd1_6_wr2_5_rd3_4 (s1, d2, s3, 3, 2, 9);   // { dg-warning "reading 9 bytes from a region of size 1" }
+}
+
+
+RDONLY (6, 1) WRONLY (5, 2) RDWR (4, 3) void
+rd6_1_wr5_2_rd4_3 (int   n1, int   n2, int         n3,
+		   void *d3, void *d2, const void *s1);
+
+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" }
+}
+
+
+RDONLY (1, 3) WRONLY (2, 4) void
+rd1_3_wr2_4 (const void*, void*, int, int);
+
+void test_rd1_3_wr2_4 (const void *s, void *d, int n1, int n2)
+{
+  rd1_3_wr2_4 (s, d, 1, 2);
+  rd1_3_wr2_4 (s, d, 123, 456);
+  rd1_3_wr2_4 (s, d, INT_MAX, INT_MAX);
+  rd1_3_wr2_4 (s, d, -1, 2);    // { dg-warning "argument 3 value -1 is negative" }
+
+  const char s11[11] = "0123456789";
+
+  rd1_3_wr2_4 (s11, d, 11, n2);
+  rd1_3_wr2_4 (s11, d, 12, n2);   // { dg-warning "'rd1_3_wr2_4' reading 12 bytes from a region of size 11" }
+}
+
+
+/* Verify that function pointers are handled.  */
+
+RDONLY (1) void (*pfrd1)(const void*, const void*);
+
+void test_pfrd1 (void)
+{
+  pfrd1 (0, 0);
+  pfrd1 ("", "");
+
+  pfrd1 ("", "" + 1);
+  pfrd1 ("" + 1, "");   // { dg-warning "reading 1 byte from a region of size 0" }
+}
+
+
+WRONLY (4, 3) void (*pfwr4_3)(int, const char*, int, int*);
+
+void test_pfwr4_3 (void)
+{
+  pfwr4_3 (0, 0, 0, 0);
+
+  int32_t i;
+  pfwr4_3 (3, "", 0, &i + 1);
+  pfwr4_3 (5, "", 1, &i + 1);   // { dg-warning "writing 4 bytes into a region of size 0" }
+}
diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-23.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-23.c
new file mode 100644
index 00000000000..f7094342861
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-23.c
@@ -0,0 +1,176 @@ 
+/* PR middle-end/83859 - attribute to establish relation between parameters
+   for buffer and its size
+   Test to verify that with optimization enabled, -Wstringop-overflow
+   warnings are issued for calls to user-defined functions with attribute
+   access and with non-constant out-of-bounds arguments.
+   { dg-do compile }
+   { dg-options "-O2 -Wall" } */
+
+#include "range.h"
+
+#define INT_MAX   __INT_MAX__
+#define INT_MIN   (-INT_MAX - 1)
+
+#define RDONLY(...)  __attribute__ ((access (read_only, __VA_ARGS__)))
+#define WRONLY(...)  __attribute__ ((access (write_only, __VA_ARGS__)))
+#define RDWR(...)  __attribute__ ((access (read_write, __VA_ARGS__)))
+
+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\\\)" }
+
+void test_rd2_1 (void)
+{
+  {
+    void *null = 0;
+    void *p = &null;
+
+    rd2_1 (0, null);
+    rd2_1 (1, p);
+  }
+
+  {
+    void *null = 0;
+    rd2_1 (1, null);            // { dg-warning "argument 2 is null but the corresponding size argument 1 value is 1" }
+  }
+
+  {
+    void *null = 0;
+    rd2_1 (SR (1, 2), null);    // { dg-warning "argument 2 is null but the corresponding size argument 1 range is \\\[1, 2]" }
+  }
+}
+
+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\\\)" }
+
+void test_wr3_1 (void)
+{
+  {
+    void *null = 0;
+    void *p = &null;
+
+    wr3_1 (SR (0, 1), 0, null);
+    wr3_1 (SR (1, 1), 0, p);
+  }
+
+  void *null = 0;
+
+  wr3_1 (SR (1, 2), 1, null);   // { dg-warning "argument 3 is null but the corresponding size argument 1 range is \\\[1, 2]" }
+}
+
+
+WRONLY (2, 1) void
+wr2_1 (int, void*);
+
+void test_wrd2_1 (int n)
+{
+  wr2_1 (0, 0);
+  wr2_1 (SR (-1, 1), 0);
+  wr2_1 (SR (0, 1), 0);
+  wr2_1 (SR (1, 2), 0);         // { dg-warning "argument 2 is null but the corresponding size argument 1 range is \\\[1, 2]" }
+
+  /* This should probably be diagnosed but to avoid false positives
+     caused by jump threading and such it would have to be done
+     earlier than it is now.  */
+  wr2_1 (n, 0);                 // { dg-warning "argument 2 is null" "unimplemented" { xfail *-*-* } }
+}
+
+
+/* Exercise pointer to an incomplete type other than void.  */
+
+struct Incomplete;
+extern struct Incomplete inc;
+
+extern char ax[];
+
+WRONLY (1, 2) void
+wr1_2_inc (struct Incomplete*, unsigned);
+
+void test_wr1_2_inc (struct Incomplete *pinc, unsigned n)
+{
+  wr1_2_inc (0, 0);
+  wr1_2_inc (0, 1);         // { dg-warning "argument 1 is null but the corresponding size argument 2 value is 1" }
+
+  wr1_2_inc (pinc, 1);
+  wr1_2_inc (&inc, 1);
+
+  wr1_2_inc (pinc, 123);
+  wr1_2_inc (&inc, 456);
+
+  char a3[3];
+  pinc = (struct Incomplete*)a3;
+  wr1_2_inc (pinc, SR (3, 4));
+  wr1_2_inc (pinc, SR (4, 5));
+  // { dg-warning "'wr1_2_inc' writing between 4 and 5 bytes into a region of size 3" "small buffer cast to incomplete" { target *-*-* } .-1 }
+
+  pinc = (struct Incomplete*)ax;
+  wr1_2_inc (pinc, SR (123, 456));
+
+  char vla[n];
+  pinc = (struct Incomplete*)vla;
+  wr1_2_inc (pinc, SR (345, 456));
+}
+
+
+RDONLY (1, 3) WRONLY (2, 4) void
+rd1_3_wr2_4 (const void*, void*, int, int);
+
+void test_rd1_3_wr2_4 (const void *s, void *d, int n1, int n2)
+{
+  rd1_3_wr2_4 (s, d, 1, 2);
+  rd1_3_wr2_4 (s, d, 123, 456);
+  rd1_3_wr2_4 (s, d, INT_MAX, INT_MAX);
+  rd1_3_wr2_4 (s, d, -1, 2);    // { dg-warning "argument 3 value -1 is negative" }
+
+  const int ir_min_m1 = SR (INT_MIN, -1);
+  rd1_3_wr2_4 (s, d, ir_min_m1, 2);   // { dg-warning "argument 3 range \\\[-\[0-9\]+, -1] is negative" }
+
+  rd1_3_wr2_4 (s, d, SR (-1, 0), 2);
+  rd1_3_wr2_4 (s, d, SR (INT_MIN, INT_MAX), 2);
+
+  rd1_3_wr2_4 (s, d, n1, n2);
+
+
+  const char s11[11] = "0123456789";
+
+  rd1_3_wr2_4 (s11, d, 11, n2);
+  rd1_3_wr2_4 (s11, d, 12, n2);   // { dg-warning "'rd1_3_wr2_4' reading 12 bytes from a region of size 11" }
+
+  rd1_3_wr2_4 (s11, d, SR (0, 11), n2);
+  rd1_3_wr2_4 (s11, d, SR (0, 12), n2);
+  rd1_3_wr2_4 (s11, d, SR (11, 12), n2);
+  rd1_3_wr2_4 (s11, d, SR (11, INT_MAX), n2);
+  rd1_3_wr2_4 (s11, d, SR (12, 13), n2);  // { dg-warning "'rd1_3_wr2_4' reading between 12 and 13 bytes from a region of size 11" }
+
+  char d4[4];
+  rd1_3_wr2_4 (s, d4, n1, 4);
+  rd1_3_wr2_4 (s, d4, n1, 5);     // { dg-warning "'rd1_3_wr2_4' writing 5 bytes into a region of size 4" }
+
+  rd1_3_wr2_4 (s11, d4, SR (12, 13), SR (5, 6));
+  // { dg-warning "'rd1_3_wr2_4' reading between 12 and 13 bytes from a region of size 11" "read" { target *-*-* } .-1 }
+  // { dg-warning "'rd1_3_wr2_4' writing between 5 and 6 bytes into a region of size 4" "read" { target *-*-* } .-2 }
+}
+
+
+/* Verify that function pointers are handled.  */
+
+RDONLY (1) void (*pfrd1)(const void*, const void*);
+
+void test_pfrd1 (void)
+{
+  pfrd1 ("" + SR (0, 9), "" + SR (1, 9));
+  pfrd1 ("" + SR (1, 2), "");   // { dg-warning "reading 1 byte from a region of size 0" }
+}
+
+
+WRONLY (4, 3) void (*pfwr4_3)(int, const char*, int, int*);
+
+void test_pfwr4_3 (void)
+{
+  int32_t i;
+  pfwr4_3 (3, "", 0, &i + SR (0, 9));
+  pfwr4_3 (5, "", 1, &i + SR (1, 2));   // { dg-warning "writing 4 bytes into a region of size 0" }
+}
diff --git a/gcc/testsuite/gcc.dg/attr-access-read-only.c b/gcc/testsuite/gcc.dg/attr-access-read-only.c
new file mode 100644
index 00000000000..9acd769621e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/attr-access-read-only.c
@@ -0,0 +1,96 @@ 
+/* PR middle-end/83859 - attribute to establish relation between parameters
+   for buffer and its size
+   Test to verify the handling of attribute access (read_only) syntax.
+   { dg-do compile }
+   { dg-options "-Wall -ftrack-macro-expansion=0" } */
+
+int  __attribute__ ((access))
+access_v (void);       // { dg-error "wrong number of arguments specified for 'access' attribute" }
+
+int  __attribute__ ((access ()))
+access___v (void);     // { dg-error "wrong number of arguments specified for 'access' attribute" }
+
+int  __attribute__ ((access (rdonly)))
+rdonly_spelling (void);   // { dg-error "attribute .access. invalid mode 'rdonly'; expected one of 'read_only', 'read_write', or 'write_only'" }
+
+int  __attribute__ ((access (read_only)))
+rdonly_v_all (void);   // { dg-error "attribute .access\\(read_only\\). missing an argument" }
+
+int  __attribute__ ((access (read_only ())))
+rdonly___v_all (void);   // { dg-error "attribute 'access' unexpected '\\(' after mode 'read_only'; expected a positional argument or '\\)'" }
+// { dg-warning "implicit declaration of function 'read_only'" "" { target *-*-* } .-2 }
+
+
+int rdonly (void);
+
+int  __attribute__ ((access (rdonly ())))
+rdonly___v_all (void);   // { dg-error "attribute 'access' invalid mode 'rdonly'" }
+
+
+int  __attribute__ ((access (read_only)))
+rdonly_i_all (int);   // { dg-error "attribute .access\\(read_only\\). missing an argument" }
+
+#define rdonly       __attribute__ ((access (read_only)))
+#define RDONLY(...)  __attribute__ ((access (read_only, __VA_ARGS__)))
+
+int RDONLY (1)
+rdonly_pcv_1 (const void*);
+int RDONLY (2)
+rdonly_i_pcv_2 (int, const void*);
+int RDONLY (3)
+rdonly_i_i_pcv_3 (int, int, const void*);
+
+int RDONLY (0 + 1)
+rdonly_pcv_0p1 (const void*);
+
+int RDONLY (2 - 1)
+rdonly_pcv_2m1 (const void*);
+
+int RDONLY (1, 1)
+rdonly_pv_pi_1_1 (const void*, const int*);      // { dg-error "attribute 'access\\(read_only, 1, 1\\)' positional argument 2 references non-integer argument type 'const void \\*'" }
+
+int RDONLY (1, 2)
+rdonly_pcv_pc_1_2 (const void*, char*);   // { dg-error "attribute .access\\(read_only, 1, 2\\)' positional argument 2 references non-integer argument type 'char \\*'" }
+
+int RDONLY (2, 1)
+rdonly_pcd_pcv_2_1 (const double*, const void*);   // { dg-error "attribute .access\\(read_only, 2, 1\\)' positional argument 2 references non-integer argument type 'const double \\*'" }
+
+int RDONLY (2, 2)
+rdonly_pi_pcv_2_2 (int*, const void*);   // { dg-error "positional argument 2 references non-integer argument type 'const void \\*'" }
+
+int RDONLY (4)
+rdonly_i_i_i_4 (int, int, int);   // { dg-error "attribute 'access\\(read_only, 4\\)' positional argument 1 value 4 exceeds number of function arguments 3" }
+
+int RDONLY (1)
+rdonly_i_1 (int);   // { dg-error "attribute 'access\\(read_only, 1\\)' positional argument 1 references non-pointer argument type 'int'" }
+
+// It's okay if the pointer argument is non-const, although a separate
+// warning encouraging one might be worthwhile.  Maybe something like
+// -Wsuggest-const.
+int RDONLY (2)
+rdonly_i_pc (int, char*);
+
+int RDONLY (-1)
+rdonly_pcv_m1 (const void*);   // { dg-error "attribute 'access\\(read_only, -1\\)' positional argument 1 invalid value -1" }
+
+int RDONLY (1, -12345)
+rdonly_pcv_i_1_m12345 (const void*, int*);   // { dg-error "attribute 'access\\(read_only, 1, -12345\\)' positional argument 2 invalid value -12345" }
+
+int RDONLY ("blah")
+rdonly_pcv_str (const void*);   // { dg-error "attribute 'access\\(read_only, \"blah\"\\)' invalid positional argument 1" }
+
+int RDONLY (1, "foobar")
+rdonly_pcv_i_1_str (const void*, int);   // { dg-error "attribute 'access\\(read_only, 1, \"foobar\"\\)' invalid positional argument 2" }
+
+// Verify that attributes whose operands reference function pointers
+// are rejected.
+typedef int F (int, int);
+RDONLY (1) void rdwr_pf_1 (F*);   // { dg-error "attribute 'access\\(read_only, 1\\)' positional argument 1 references argument of function type 'F' \\{aka 'int\\(int,  *int\\)'\\}" }
+
+// Verify pointers to functions.
+void RDONLY(2) (*prdonly_pcv2)(int, const void*);
+void RDONLY(3, 1) (*prdonly_pcv2_1)(int, void*, const void*);
+
+// Verify types.
+typedef RDONLY (2) void rdonly_p2_t (const int*, const char*, const void*);
+typedef RDONLY (2) void rdonly_p2_1 (int, const int*);
diff --git a/gcc/testsuite/gcc.dg/attr-access-read-write-2.c b/gcc/testsuite/gcc.dg/attr-access-read-write-2.c
new file mode 100644
index 00000000000..c2ac6c344a5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/attr-access-read-write-2.c
@@ -0,0 +1,61 @@ 
+/* PR middle-end/83859 - attribute to establish relation between parameters
+   for buffer and its size
+   Test to verify the handling of attribute read_only combining multiple
+   declarations of the same function.
+   { dg-do compile }
+   { dg-options "-Wall -ftrack-macro-expansion=0" } */
+
+#define RW(...)    __attribute__ ((access (read_write, __VA_ARGS__)))
+#define WO(...)  __attribute__ ((access (write_only, __VA_ARGS__)))
+
+int rdwr1_rdwr1 (void*, void*);
+int RW (1) RW (1) rdwr1_rdwr1 (void*, void*);
+int RW (2) RW (2) rdwr1_rdwr1 (void*, void*);
+int RW (1) RW (2) rdwr1_rdwr1 (void*, void*);
+int RW (2) RW (1) rdwr1_rdwr1 (void*, void*);
+
+int frdwr1_wr1 (void*, void*);
+int RW (1) WO (1) frdwr1_wr1 (void*, void*);    // { dg-warning "attribute 'access\\(write_only, 1\\)' mismatch with mode 'read_write'" }
+
+int RW (1) grdwr1_wr1 (void*, void*);           // { dg-message "previous declaration here" }
+
+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, 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" }
+
+
+typedef int *P;
+
+int RW(1) WO(3) RW(5) WO(7) RW(9) WO(11) RW(13) WO(15) frw1_w3_rw5_w7_rw9_wr11_rw13_w15 (P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, int);
+
+int RW(1) WO(3) RW(5) WO(7) RW(9) WO(11) RW(13) WO(15) frw1_w3_rw5_w7_rw9_wr11_rw13_w15 (P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, int);
+
+int WO(1) WO(3) RW(5) WO(7) RW(9) WO(11) RW(13) WO(15) frw1_w3_rw5_w7_rw9_wr11_rw13_w15 (P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, int);
+// { dg-warning "attribute 'access\\(write_only, 1\\)' mismatch with mode 'read_write'" "1" { target *-*-* } .-1 }
+
+int RW(1) RW(3) RW(5) WO(7) RW(9) WO(11) RW(13) WO(15) frw1_w3_rw5_w7_rw9_wr11_rw13_w15 (P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, int);
+// { dg-warning "attribute 'access\\(read_write, 3\\)' mismatch with mode 'write_only'" "3" { target *-*-* } .-1 }
+
+int RW(1) WO(3) WO(5) WO(7) RW(9) WO(11) RW(13) WO(15) frw1_w3_rw5_w7_rw9_wr11_rw13_w15 (P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, int);
+// { dg-warning "attribute 'access\\(write_only, 5\\)' mismatch with mode 'read_write'" "5" { target *-*-* } .-1 }
+
+int RW(1) WO(3) RW(5) RW(7) RW(9) WO(11) RW(13) WO(15) frw1_w3_rw5_w7_rw9_wr11_rw13_w15 (P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, int);
+// { dg-warning "attribute 'access\\(read_write, 7\\)' mismatch with mode 'write_only'" "7" { target *-*-* } .-1 }
+
+int RW(1) WO(3) RW(5) WO(7) WO(9) WO(11) RW(13) WO(15) frw1_w3_rw5_w7_rw9_wr11_rw13_w15 (P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, int);
+// { dg-warning "attribute 'access\\(write_only, 9\\)' mismatch with mode 'read_write'" "9" { target *-*-* } .-1 }
+
+int RW(1) WO(3) RW(5) WO(7) RW(9) RW(11) RW(13) WO(15) frw1_w3_rw5_w7_rw9_wr11_rw13_w15 (P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, int);
+// { dg-warning "attribute 'access\\(read_write, 11\\)' mismatch with mode 'write_only'" "11" { target *-*-* } .-1 }
+
+int RW(1) WO(3) RW(5) WO(7) RW(9) WO(11) WO(13) WO(15) frw1_w3_rw5_w7_rw9_wr11_rw13_w15 (P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, int);
+// { dg-warning "attribute 'access\\(write_only, 13\\)' mismatch with mode 'read_write'" "13" { target *-*-* } .-1 }
+
+int RW(1) WO(3) RW(5) WO(7) RW(9) WO(11) RW(13) RW(15) frw1_w3_rw5_w7_rw9_wr11_rw13_w15 (P, P, P, P, P, P, P, P, P, P, P, P, P, P, P, int);
+// { dg-warning "attribute 'access\\(read_write, 15\\)' mismatch with mode 'write_only'" "15" { target *-*-* } .-1 }
diff --git a/gcc/testsuite/gcc.dg/attr-access-read-write.c b/gcc/testsuite/gcc.dg/attr-access-read-write.c
new file mode 100644
index 00000000000..c97e54bbd60
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/attr-access-read-write.c
@@ -0,0 +1,92 @@ 
+/* PR middle-end/83859 - attribute to establish relation between parameters
+   for buffer and its size
+   { dg-do compile }
+   { dg-options "-Wall -ftrack-macro-expansion=0" } */
+
+int  __attribute__ ((access))
+access_v (void);       /* { dg-error "wrong number of arguments specified for 'access' attribute" } */
+
+int  __attribute__ ((access ()))
+access___v (void);     /* { dg-error "wrong number of arguments specified for 'access' attribute" } */
+
+int  __attribute__ ((access (rdwr)))
+rdwr_spelling (void);   /* { dg-error "attribute .access. invalid mode 'rdwr'; expected one of 'read_only', 'read_write', or 'write_only'" } */
+
+int  __attribute__ ((access (read_write)))
+rdwr_v_all (void);   /* { dg-error "attribute .access\\(read_write\\). missing an argument" } */
+
+int  __attribute__ ((access (read_write ())))
+rdwr___v_all (void);   /* { dg-error "attribute 'access' unexpected '\\(' after mode 'read_write'; expected a positional argument or '\\)'" } */
+/* { dg-warning "implicit declaration of function 'read_write'" "" { target *-*-* } .-2 } */
+
+
+int rdwr (void);
+
+int  __attribute__ ((access (rdwr ())))
+rdwr___v_all (void);   /* { dg-error "attribute 'access' invalid mode 'rdwr'" } */
+
+
+#define RDWR(...)  __attribute__ ((access (read_write, __VA_ARGS__)))
+
+int RDWR (1)
+rdwr_pcv_1 (void*);
+
+int RDWR (2)
+rdwr_i_pcv_2 (int, void*);
+int RDWR (3)
+rdwr_i_i_pcv_3 (int, int, void*);
+
+int RDWR (0 + 1)
+rdwr_pcv_0p1 (void*);
+
+int RDWR (2 - 1)
+rdwr_pcv_2m1 (void*);
+
+int RDWR (1)
+rdwr_pcv_pi_1_1 (const void*, int*);    /* { dg-error "attribute 'access\\(read_write, 1\\)' positional argument 1 references 'const'-qualified argument type 'const void \\*'" } */
+
+int RDWR (1, 1)
+rdwr_pv_pi_1_1 (void*, int*);      /* { dg-error "attribute 'access\\(read_write, 1, 1\\)' positional argument 2 references non-integer argument type 'void \\*'" } */
+
+int RDWR (1, 2)
+rdwr_pcv_pc_1_2 (void*, char*);   /* { dg-error "attribute .access\\(read_write, 1, 2\\)' positional argument 2 references non-integer argument type 'char \\*'" } */
+
+int RDWR (2, 1)
+rdwr_pcd_pcv_2_1 (double*, void*);   /* { dg-error "attribute .access\\(read_write, 2, 1\\)' positional argument 2 references non-integer argument type 'double \\*'" } */
+
+int RDWR (2, 2)
+rdwr_pi_pcv_2_2 (int*, void*);   /* { dg-error "positional argument 2 references non-integer argument type 'void \\*'" } */
+
+int RDWR (4)
+rdwr_i_i_i_4 (int, int, int);   /* { dg-error "attribute 'access\\(read_write, 4\\)' positional argument 1 value 4 exceeds number of function arguments 3" } */
+
+int RDWR (1)
+rdwr_i_1 (int);   /* { dg-error "attribute 'access\\(read_write, 1\\)' positional argument 1 references non-pointer argument type 'int'" } */
+
+int RDWR (2)
+rdwr_i_pc (int, const char*);   /* { dg-error "attribute 'access\\(read_write, 2\\)' positional argument 1 references 'const'-qualified argument type 'const char \\*'" } */
+
+int RDWR (-1)
+rdwr_pcv_m1 (void*);   /* { dg-error "attribute 'access\\(read_write, -1\\)' positional argument 1 invalid value -1" } */
+
+int RDWR (1, -12345)
+rdwr_pcv_i_1_m12345 (void*, int*);   /* { dg-error "attribute 'access\\(read_write, 1, -12345\\)' positional argument 2 invalid value -12345" } */
+
+int RDWR ("blah")
+rdwr_pcv_str (void*);   /* { dg-error "attribute 'access\\(read_write, \"blah\"\\)' invalid positional argument 1" } */
+
+int RDWR (1, "foobar")
+rdwr_pcv_i_1_str (void*, int);   /* { dg-error "attribute 'access\\(read_write, 1, \"foobar\"\\)' invalid positional argument 2" } */
+
+/* Verify that attributes whose operands reference function pointers
+   are rejected.  */
+typedef int F (int, int);
+RDWR (1) void rdwr_pf_1 (F*);   /* { dg-error "attribute 'access\\(read_write, 1\\)' positional argument 1 references argument of function type 'F' \\{aka 'int\\(int,  *int\\)'\\}" } */
+
+/* Verify pointers to functions.  */
+void RDWR(2) (*prdwr_pv2)(int, void*);
+void RDWR(3, 1) (*prdwr_pv2_1)(int, void*, void*);
+
+/* Verify types.  */
+typedef RDWR (2) void rdwr_p2_t (int*, char*, void*);
+typedef RDWR (2) void rdwr_p2_1 (int, int*);
diff --git a/gcc/testsuite/gcc.dg/attr-access-write-only.c b/gcc/testsuite/gcc.dg/attr-access-write-only.c
new file mode 100644
index 00000000000..008f5a36ff4
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/attr-access-write-only.c
@@ -0,0 +1,89 @@ 
+/* PR middle-end/83859 - attribute to establish relation between parameters
+   for buffer and its size
+   Test to verify the handling of attribute access (write_only) syntax.
+   { dg-do compile }
+   { dg-options "-Wall -ftrack-macro-expansion=0" } */
+
+int  __attribute__ ((access))
+access_v (void);       // { dg-error "wrong number of arguments specified for 'access' attribute" }
+
+int  __attribute__ ((access ()))
+access___v (void);     // { dg-error "wrong number of arguments specified for 'access' attribute" }
+
+int  __attribute__ ((access (wronly)))
+wronly_spelling (void);   // { dg-error "attribute .access. invalid mode 'wronly'; expected one of 'read_only', 'read_write', or 'write_only'" }
+
+int  __attribute__ ((access (read_only)))
+wronly_v_all (void);   // { dg-error "attribute .access\\(read_only\\). missing an argument" }
+
+int  __attribute__ ((access (read_only ())))
+wronly___v_all (void);   // { dg-error "attribute 'access' unexpected '\\(' after mode 'read_only'; expected a positional argument or '\\)'" }
+// { dg-warning "implicit declaration of function 'read_only'" "" { target *-*-* } .-2 }
+
+
+int wronly (void);
+
+int  __attribute__ ((access (wronly ())))
+wronly___v_all (void);   // { dg-error "attribute 'access' invalid mode 'wronly'" }
+
+#define WRONLY(...)  __attribute__ ((access (write_only, __VA_ARGS__)))
+
+int WRONLY (1)
+wronly_pcv_1 (void*);
+int WRONLY (2)
+wronly_i_pcv_2 (int, void*);
+int WRONLY (3)
+wronly_i_i_pcv_3 (int, int, void*);
+
+int WRONLY (0 + 1)
+wronly_pcv_0p1 (void*);
+
+int WRONLY (2 - 1)
+wronly_pcv_2m1 (void*);
+
+int WRONLY (1, 1)
+wronly_pv_pi_1_1 (void*, const int*);      // { dg-error "attribute 'access\\(write_only, 1, 1\\)' positional argument 2 references non-integer argument type 'void \\*'" }
+
+int WRONLY (1, 2)
+wronly_pcv_pc_1_2 (void*, char*);   // { dg-error "attribute .access\\(write_only, 1, 2\\)' positional argument 2 references non-integer argument type 'char \\*'" }
+
+int WRONLY (2, 1)
+wronly_pcd_pcv_2_1 (const double*, void*);   // { dg-error "attribute .access\\(write_only, 2, 1\\)' positional argument 2 references non-integer argument type 'const double \\*'" }
+
+int WRONLY (2, 2)
+wronly_pi_pcv_2_2 (int*, void*);   // { dg-error "positional argument 2 references non-integer argument type 'void \\*'" }
+
+int WRONLY (4)
+wronly_i_i_i_4 (int, int, int);   // { dg-error "attribute 'access\\(write_only, 4\\)' positional argument 1 value 4 exceeds number of function arguments 3" }
+
+int WRONLY (1)
+wronly_i_1 (int);   // { dg-error "attribute 'access\\(write_only, 1\\)' positional argument 1 references non-pointer argument type 'int'" }
+
+int WRONLY (2)
+wronly_i_pc (int, const char*);   // { dg-error "attribute 'access\\(write_only, 2\\)' positional argument 1 references 'const'-qualified argument type 'const char \\*'" }
+
+int WRONLY (-1)
+wronly_pcv_m1 (void*);   // { dg-error "attribute 'access\\(write_only, -1\\)' positional argument 1 invalid value -1" }
+
+int WRONLY (1, -12345)
+wronly_pcv_i_1_m12345 (void*, int*);   // { dg-error "attribute 'access\\(write_only, 1, -12345\\)' positional argument 2 invalid value -12345" }
+
+int WRONLY ("blah")
+wronly_pcv_str (void*);   // { dg-error "attribute 'access\\(write_only, \"blah\"\\)' invalid positional argument 1" }
+
+int WRONLY (1, "foobar")
+wronly_pcv_i_1_str (void*, int);   // { dg-error "attribute 'access\\(write_only, 1, \"foobar\"\\)' invalid positional argument 2" }
+
+// Verify that attributes whose operands reference function pointers
+// are rejected.
+typedef int F (int, int);
+WRONLY (1) void wronly_pf_1 (F*);   // { dg-error "attribute 'access\\(write_only, 1\\)' positional argument 1 references argument of function type 'F' \\{aka 'int\\(int,  *int\\)'\\}" }
+
+// Verify pointers to functions.
+void WRONLY(2) (*pwronly_pcv2)(int, void*);
+void WRONLY(3, 1) (*pwronly_pcv2_1)(int, void*, void*);
+void WRONLY(1, 2) (*pwronly_i_pcv_1_2)(int, void*);   // { dg-error "attribute 'access\\(write_only, 1, 2\\)' positional argument 1 references non-pointer argument type 'int'" }
+
+// Verify types.
+typedef WRONLY (2) void wronly_p2_t (const int*, char*, const void*);
+typedef WRONLY (2) void wronly_p2_1 (int, int*);