public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [WIP PATCH] add object access attributes (PR 83859)
@ 2019-09-29 19:52 Martin Sebor
  2019-09-30  7:37 ` Richard Biener
                   ` (3 more replies)
  0 siblings, 4 replies; 19+ messages in thread
From: Martin Sebor @ 2019-09-29 19:52 UTC (permalink / raw)
  To: gcc-patches

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

-Wstringop-overflow detects a subset of past-the-end read and write
accesses by built-in functions such as memcpy and strcpy.  It relies
on the functions' effects the knowledge of which is hardwired into
GCC.  Although it's possible for users to create wrappers for their
own functions to detect similar problems, it's quite cumbersome and
so only lightly used outside system libraries like Glibc.  Even Glibc
only checks for buffer overflow and not for reading past the end.

PR 83859 asks to expose the same checking that GCC does natively for
built-in calls via a function attribute that associates a pointer
argument with the size argument, such as:

   __attribute__((buffer_size (1, 2))) void
   f (char* dst, size_t dstsize);

The attached patch is my initial stab at providing this feature by
introducing three new attributes:

   * read_only (ptr-argno, size-argno)
   * read_only (ptr-argno, size-argno)
   * read_write (ptr-argno, size-argno)

As requested, the attributes associate a pointer parameter to
a function with a size parameter.  In addition, they also specify
how the function accesses the object the pointer points to: either
it only reads from it, or it only writes to it, or it does both.

Besides enabling the same buffer overflow detection as for built-in
string functions they also let GCC issue -Wuninitialized warnings
for uninitialized objects passed to read-only functions by reference,
and -Wunused-but-set warnings for objects passed to write-only
functions that are otherwise unused (PR 80806).  The -Wununitialized
part is done. The -Wunused-but-set detection is implemented only in
the C FE and not yet in C++.

Besides the diagnostic improvements above the attributes also open
up optimization opportunities such as DCE.  I'm still working on this
and so it's not yet part of the initial patch.

I plan to finish the patch for GCC 10 but I don't expect to have
the time to start taking advantage of the attributes for optimization
until GCC 11.

Besides regression testing on x86_64-linux, I also tested the patch
by compiling Binutils/GDB, Glibc, and the Linux kernel with it.  It
found no new problems but caused a handful of -Wunused-but-set-variable 
false positives due to an outstanding bug in the C front-end introduced
by the patch that I still need to fix.

Martin

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

PR c/80806 - gcc does not warn if local array is memset only
PR middle-end/83859 - attribute to associate buffer and its size

gcc/ChangeLog:

	PR c/80806
	PR middle-end/83859
	* builtin-attrs.def (ATTR_NO_SIDE_EFFECT): New.
	(ATTR_READ_ONLY, ATTR_READ_WRITE, ATTR_WRITE_ONLY): New.
	(ATTR_NOTHROW_WRONLY1_LEAF, ATTR_NOTHROW_WRONLY1_2_LEAF): New.
	(ATTR_NOTHROW_WRONLY1_3_LEAF, ATTR_NOTHROW_WRONLY2_3_LEAF): New.
	(ATTR_RET1_NOTHROW_WRONLY1_LEAF, ATTR_RET1_NOTHROW_WRONLY1_3_LEAF): New.
	(ATTR_RET1_NOTHROW_NONNULL_RDONLY2_LEAF): New.
	(ATTR_RET1_NOTHROW_NONNULL_RDWR1_RDONLY2_LEAF): New.
	(ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_3_LEAF): New.
	(ATTR_RET1_NOTHROW_WRONLY1_RDONLY2_LEAF): New.
	(ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_LEAF): New.
	(ATTR_MALLOC_NOTHROW_NONNULL_RDONLY1_LEAF): New.
	(ATTR_PURE_NOTHROW_NONNULL_RDONLY1_LEAF): New.
	(ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF): New.
	(ATTR_RETNONNULL_RDONLY2_3_NOTHROW_LEAF): New.
	(ATTR_RETNONNULL_WRONLY1_3_RDONLY2_3_NOTHROW_LEAF): New.
	(ATTR_PURE_NOTHROW_NONNULL_RDONLY1_3_LEAF): New.
	(ATTR_PURE_NOTHROW_NONNULL_RDONLY1_2_3_LEAF): New.
	(ATTR_RETNONNULL_RDONLY2_NOTHROW_LEAF): New.
	(ATTR_RETNONNULL_WRONLY1_RDONLY2_NOTHROW_LEAF): New.
	(ATTR_RETNONNULL_WRONLY1_3_RDONLY2_NOTHROW_LEAF): New.
	* builtins.c (check_access): Make extern.  Consistently set
	the no-warning bit after issuing a warning.
	* builtins.h (check_access): Declare.
	* builtins.def (bcopy, bzero, index, memchr, memcmp, memcpy): Add
	read_only and write_only attributes.
	(memset, rindex, stpcpy, stpncpy, strcasecmp, strcat): Same.
	(strchr, strcmp, strcpy, strcspn, strdup, strndup, strlen): Same.
	(strncasecmp, strncat, strncmp, strncpy, strrchr, strspn, strstr): Same.
	(free, __memcpy_chk, __memmove_chk, __memset_chk): Same.
	(__strcpy_chk, __strncpy_chk): Same.
	* 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 (no_side_effect): Document new attribute.
	(read_only, write_only, read_write): Same.
	* tree-ssa-uninit.c (maybe_warn_uninit_accesss): New functions.
	(warn_uninitialized_vars): Rename argument.  Factor out code into
	maybe_warn_uninit_accesss.  Call it.

gcc/c/ChangeLog:

	PR c/80806
	PR middle-end/83859
	* c-parser.c (c_parser::no_set_read, no_init, in_arg): New members.
	(c_parser_expr_list): Add argument.
	(c_parser_attribute): Pass a new argument to get_nonnull_operand.
	(c_parser_initializer): Set parser->in_init.
	(c_parser_binary_expression): Use parser->no_set_read.
	(c_parser_unary_expression): Same.
	(c_parser_sizeof_expression): Use parser->in_arg.
	(c_parser_postfix_expression_after_primary): Adjust.
	(is_write_only_p): New function.
	(c_parser_expr_list): Add argument.
	Avoid setting DECL_READ_P for decls passed to write-only function
	arguments.
	(c_parser_objc_keywordexpr): Pass a new argument to c_parser_expr_list.
	(c_parser_oacc_wait_list): Same.
	* c-tree.h (parser_build_binary_op): Add argument.
	* c-typeck.c (default_conversion): Same.  Use it.
	(parser_build_binary_op): Same.
	(build_binary_op): Same.

gcc/c-family/ChangeLog:

	PR c/80806
	PR middle-end/83859
	* c-attribs.c (handle_no_side_effect_attribute): New function.
	(handle_read_only_attribute, handle_write_only_attribute): Same.
	(handle_read_write_attribute): Same.
	(c_common_attribute_table): Add new attributes.
	(get_argument_type): New function.
	(handle_rdwr_attributes): Same.
	(has_attribute): Add argument to callback signature.  Pass to it
	a new value.
	(get_nonnull_operand): Rename...
	(get_attribute_operand): ...to this.
	* c-common.c (get_nonnull_operand): Rename...
	(get_attribute_operand): ...to this.
	(build_binary_op): Add a new argument.
	(default_conversion): Same.
	(has_attribute): Adjust argument type.

gcc/cp/ChangeLog:

	PR c/80806
	PR middle-end/83859
	* typeck.c (cp_default_conversion): Add argument.
	(build_binary_op): Same.

gcc/testsuite/ChangeLog:

	PR c/80806
	PR middle-end/83859
	* c-c++-common/Wsizeof-pointer-memaccess1.c: Adjust.
	* c-c++-common/Wsizeof-pointer-memaccess2.c: Adjust.
	* gcc.dg/Wstrict-aliasing-bogus-vla-1.c: Adjust.
	* gcc/testsuite/gcc.dg/Wunused-but-set-var.c: New test.
	* gcc.dg/attr-alloc_size.c: Adjust.
	* gcc/testsuite/gcc.dg/attr-read-only-2.c: New test.
	* gcc/testsuite/gcc.dg/attr-read-only.c: New test.
	* gcc/testsuite/gcc.dg/attr-write-only-2.c: New test.
	* gcc/testsuite/gcc.dg/attr-write-only.c: New test.
	* gcc.dg/nonnull-3.c: Adjust.
	* gcc.dg/pr40340-2.c: Adjust.
	* gcc.dg/pr78768.c: Adjust.
	* gcc.dg/pr79715.c: Adjust.
	* gcc.dg/tree-ssa/builtin-snprintf-7.c: Adjust.
	* gcc/testsuite/gcc.dg/uninit-builtin.c: New test.

diff --git a/gcc/attribs.c b/gcc/attribs.c
index b89be5834de..6a1cb3270d2 100644
--- a/gcc/attribs.c
+++ b/gcc/attribs.c
@@ -33,6 +33,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic.h"
 #include "pretty-print.h"
 #include "intl.h"
+#include "gimple.h"
 
 /* Table of the tables of attributes (common, language, format, machine)
    searched.  */
@@ -2021,6 +2022,135 @@ maybe_diag_alias_attributes (tree alias, tree target)
     }
 }
 
+/* Given function type FNTYPE, initialize ACCESS based on the function's
+   first access attribute specifier, if one exists, or advance it to the
+   next specifier if it's already been initialized.  Return true if
+   ACCESS has been initialized or advanced to a valid access specifier,
+   false otherwise.  */
+
+bool
+get_attr_access_ptr_and_size (tree fntype, attr_access *access)
+{
+  if (!access->attrs)
+    access->attrs = TYPE_ATTRIBUTES (fntype);
+  else if (TREE_CODE (access->attrs) != TREE_LIST)
+    {
+      /* The last call sets ACCESS->ATTRS to something other than
+	 TREE_LIST to indicate it's reached the end.  */
+      return false;
+    }
+
+  if (!access->attrs)
+    return false;
+
+  const char *attr_name;
+  switch (access->kind)
+    {
+    case attr_access::read_only:
+      attr_name = "read_only";
+      break;
+
+    case attr_access::read_write:
+      attr_name = "read_write";
+      break;
+
+    case attr_access::write_only:
+      attr_name = "write_only";
+      break;
+
+    default:
+      gcc_unreachable ();
+    }
+
+  access->attrs = lookup_attribute (attr_name, access->attrs);
+  if (!access->attrs)
+    return false;
+
+  /* When operands are specified, iterate over them.  */
+  tree opers = TREE_VALUE (access->attrs);
+  if (opers)
+    access->ptrarg = TREE_INT_CST_LOW (TREE_VALUE (opers)) - 1;
+  else
+    {
+      /* When no operands are specified, the attribute applies to every
+	 pointer argument.  Use ARGCHAIN to find the next such argument
+	 if it exists.  */
+      if (access->argchain)
+	access->argchain = TREE_CHAIN (access->argchain);
+      else
+	{
+	  access->ptrarg = 0;
+	  access->argchain = TYPE_ARG_TYPES (fntype);
+	}
+
+      while (!POINTER_TYPE_P (TREE_VALUE (access->argchain)))
+	{
+	  access->argchain = TREE_CHAIN (access->argchain);
+	  if (!access->argchain)
+	    return false;
+	  ++access->ptrarg;
+	}
+    }
+
+  if (opers)
+    opers = TREE_CHAIN (opers);
+
+  if (opers)
+    access->sizarg = TREE_INT_CST_LOW (TREE_VALUE (opers)) - 1;
+  else
+    access->sizarg = UINT_MAX;
+
+  access->ptr = NULL_TREE;
+  access->size = NULL_TREE;
+  access->attrs = TREE_CHAIN (access->attrs);
+
+  /* When the last attribute/argument has been reached set ACCESS->ATTRS
+     to something other than TREE_LIST to indicate that to the next call.  */
+  if (!access->attrs && !access->argchain)
+    access->attrs = void_type_node;
+
+  return true;
+}
+
+/* Given a Gimple statement STMT that's a function call, initialize ACCESS
+   based on the called function's first access attribute specifier, if one
+   exists, or advance it to the next specifier if it's already been
+   initialized.  Return true if ACCESS has been initialized or advanced
+   to a valid access specifier, false otherwise.  */
+
+bool
+get_attr_access_ptr_and_size (const gimple *stmt, attr_access *access)
+{
+  const gcall *gc = as_a <const gcall *> (stmt);
+  if (!gc)
+    return false;
+
+  tree fntype = gimple_call_fntype (gc);
+  if (!fntype)
+    return false;
+
+  if (!get_attr_access_ptr_and_size (fntype, access))
+    return false;
+
+  if (access->ptrarg < gimple_call_num_args (gc))
+    {
+      tree ptr = gimple_call_arg (gc, access->ptrarg);
+      access->ptr = POINTER_TYPE_P (TREE_TYPE (ptr)) ? ptr : NULL_TREE;
+    }
+  else
+    access->ptr = NULL_TREE;
+
+  if (access->sizarg <= gimple_call_num_args (gc))
+    {
+      tree size = gimple_call_arg (gc, access->sizarg);
+      access->size = INTEGRAL_TYPE_P (TREE_TYPE (size)) ? size : NULL_TREE;
+    }
+  else
+    access->size = NULL_TREE;
+
+  return access->ptr;
+}
+
 
 #if CHECKING_P
 
diff --git a/gcc/attribs.h b/gcc/attribs.h
index 23a7321e04a..0e2320701b1 100644
--- a/gcc/attribs.h
+++ b/gcc/attribs.h
@@ -218,4 +218,36 @@ lookup_attribute_by_prefix (const char *attr_name, tree list)
     }
 }
 
+/* Description of a function argument declared attribute read_only,
+   read_write, or write_only.  Used as an "iterator" over all such
+   arguments in a function declaration or call.  */
+
+struct attr_access
+{
+  /* Attribute chain for the given function declaration.  */
+  tree attrs;
+
+  /* The attribute pointer argument.  */
+  tree ptr;
+  /* The size of the pointed-to object or NULL when not specified.  */
+  tree size;
+
+  /* The tree node corresponding to the current argument in the chain
+     of formal function arguments in a call to the given function.
+     Used by attributes that specify that relevant arguments are of
+     the given kind, as in
+       strcmp (const char*, const char*) __attribute__ ((read_only))  */
+  tree argchain;
+
+  /* The zero-based number of each of the formal function arguments.  */
+  unsigned ptrarg;
+  unsigned sizarg;
+  enum Kind { read_only, write_only, read_write };
+
+  Kind kind;
+};
+
+extern bool get_attr_access_ptr_and_size (tree, attr_access *);
+extern bool get_attr_access_ptr_and_size (const gimple *, attr_access *);
+
 #endif // GCC_ATTRIBS_H
diff --git a/gcc/builtin-attrs.def b/gcc/builtin-attrs.def
index 39d1395f42a..1d110dd3398 100644
--- a/gcc/builtin-attrs.def
+++ b/gcc/builtin-attrs.def
@@ -119,6 +119,10 @@ DEF_ATTR_IDENT (ATTR_TM_TMPURE, "transaction_pure")
 DEF_ATTR_IDENT (ATTR_RETURNS_TWICE, "returns_twice")
 DEF_ATTR_IDENT (ATTR_RETURNS_NONNULL, "returns_nonnull")
 DEF_ATTR_IDENT (ATTR_WARN_UNUSED_RESULT, "warn_unused_result")
+DEF_ATTR_IDENT (ATTR_NO_SIDE_EFFECT, "no_side_effect")
+DEF_ATTR_IDENT (ATTR_READ_ONLY, "read_only")
+DEF_ATTR_IDENT (ATTR_READ_WRITE, "read_write")
+DEF_ATTR_IDENT (ATTR_WRITE_ONLY, "write_only")
 
 DEF_ATTR_TREE_LIST (ATTR_NOVOPS_LIST, ATTR_NOVOPS, ATTR_NULL, ATTR_NULL)
 
@@ -212,6 +216,7 @@ DEF_ATTR_TREE_LIST (ATTR_NOTHROW_NONNULL, ATTR_NONNULL, ATTR_NULL, \
 DEF_ATTR_TREE_LIST (ATTR_NOTHROW_NONNULL_LEAF, ATTR_NONNULL, ATTR_NULL, \
 			ATTR_NOTHROW_LEAF_LIST)
 DEF_ATTR_TREE_LIST (ATTR_NOTHROW_NONNULL_LEAF_LIST, ATTR_LEAF, ATTR_NULL, ATTR_NOTHROW_NONNULL_LEAF)
+
 /* Nothrow functions whose first parameter is a nonnull pointer.  */
 DEF_ATTR_TREE_LIST (ATTR_NOTHROW_NONNULL_1, ATTR_NONNULL, ATTR_LIST_1, \
 			ATTR_NOTHROW_LIST)
@@ -271,10 +276,54 @@ DEF_ATTR_TREE_LIST (ATTR_NOTHROW_NONNULL_TYPEGENERIC_LEAF,
 /* Nothrow const functions whose pointer parameter(s) are all nonnull.  */
 DEF_ATTR_TREE_LIST (ATTR_CONST_NOTHROW_NONNULL, ATTR_CONST, ATTR_NULL, \
 			ATTR_NOTHROW_NONNULL)
+
 /* Nothrow leaf functions whose pointer parameter(s) are all nonnull,
-   and which return their first argument.  */
+   which return their first argument, and which access their arguments
+   for reading and writing as indicated by RDONLY and WRONLY.  */
 DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_NONNULL_LEAF, ATTR_FNSPEC, ATTR_LIST_STR1, \
 			ATTR_NOTHROW_NONNULL_LEAF)
+
+DEF_ATTR_TREE_LIST (ATTR_NOTHROW_WRONLY1_LEAF, ATTR_WRITE_ONLY, \
+		    ATTR_LIST_1, ATTR_NOTHROW_LEAF_LIST)
+
+DEF_ATTR_TREE_LIST (ATTR_NOTHROW_WRONLY1_2_LEAF, ATTR_WRITE_ONLY, \
+		    ATTR_LIST_1_2, ATTR_NOTHROW_LEAF_LIST)
+
+DEF_ATTR_TREE_LIST (ATTR_NOTHROW_WRONLY1_3_LEAF, ATTR_WRITE_ONLY, \
+		    ATTR_LIST_1_3, ATTR_NOTHROW_LEAF_LIST)
+
+DEF_ATTR_TREE_LIST (ATTR_NOTHROW_WRONLY2_3_LEAF, ATTR_WRITE_ONLY, \
+		    ATTR_LIST_2_3, ATTR_NOTHROW_NONNULL_LEAF)
+
+DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_WRONLY1_LEAF, ATTR_WRITE_ONLY, \
+		    ATTR_LIST_1, ATTR_RET1_NOTHROW_NONNULL_LEAF)
+
+DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_WRONLY1_3_LEAF, ATTR_WRITE_ONLY, \
+		    ATTR_LIST_1_3, ATTR_RET1_NOTHROW_NONNULL_LEAF)
+
+/* strcat.  */
+DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_NONNULL_RDONLY2_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_2,
+		    ATTR_RET1_NOTHROW_NONNULL_LEAF)
+DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_NONNULL_RDWR1_RDONLY2_LEAF,
+		    ATTR_READ_WRITE, ATTR_LIST_1,
+		    ATTR_RET1_NOTHROW_NONNULL_RDONLY2_LEAF)
+
+/* memcpy, memmove.  */
+DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_3_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_2_3,
+		    ATTR_RET1_NOTHROW_WRONLY1_3_LEAF)
+
+/* strcpy.  */
+DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_WRONLY1_RDONLY2_LEAF,
+ 		    ATTR_READ_ONLY, ATTR_LIST_2,
+		    ATTR_RET1_NOTHROW_WRONLY1_LEAF)
+
+/* strncpy.  */
+DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_2,
+		    ATTR_RET1_NOTHROW_WRONLY1_3_LEAF)
+
 /* Nothrow leaf functions whose pointer parameter(s) are all nonnull,
    and return value is also nonnull.  */
 DEF_ATTR_TREE_LIST (ATTR_RETNONNULL_NOTHROW_LEAF, ATTR_RETURNS_NONNULL, ATTR_NULL, \
@@ -305,6 +354,52 @@ DEF_ATTR_TREE_LIST (ATTR_WARN_UNUSED_RESULT_NOTHROW_NONNULL_LEAF, ATTR_WARN_UNUS
 DEF_ATTR_TREE_LIST (ATTR_MALLOC_WARN_UNUSED_RESULT_NOTHROW_NONNULL_LEAF, ATTR_MALLOC, ATTR_NULL, \
 			ATTR_WARN_UNUSED_RESULT_NOTHROW_NONNULL_LEAF)
 
+/* strdup, strndup.  */
+DEF_ATTR_TREE_LIST (ATTR_MALLOC_NOTHROW_NONNULL_RDONLY1_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_1,
+		    ATTR_MALLOC_WARN_UNUSED_RESULT_NOTHROW_NONNULL_LEAF)
+
+/* strchr, strlen. */
+DEF_ATTR_TREE_LIST (ATTR_PURE_NOTHROW_NONNULL_RDONLY1_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_1,
+		    ATTR_PURE_NOTHROW_NONNULL_LEAF)
+
+/* strcmp, strstr. */
+DEF_ATTR_TREE_LIST (ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_2,
+		    ATTR_PURE_NOTHROW_NONNULL_RDONLY1_LEAF)
+
+/* mempcpy.  */
+DEF_ATTR_TREE_LIST (ATTR_RETNONNULL_RDONLY2_3_NOTHROW_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_2_3,
+		    ATTR_RETNONNULL_NOTHROW_LEAF)
+DEF_ATTR_TREE_LIST (ATTR_RETNONNULL_WRONLY1_3_RDONLY2_3_NOTHROW_LEAF,
+		    ATTR_WRITE_ONLY, ATTR_LIST_1_3,
+		    ATTR_RETNONNULL_RDONLY2_3_NOTHROW_LEAF)
+
+/* memchr.  */
+DEF_ATTR_TREE_LIST (ATTR_PURE_NOTHROW_NONNULL_RDONLY1_3_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_1_3,
+		    ATTR_NOTHROW_NONNULL_LEAF)
+
+/* memcmp.  */
+DEF_ATTR_TREE_LIST (ATTR_PURE_NOTHROW_NONNULL_RDONLY1_2_3_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_2_3, \
+		    ATTR_PURE_NOTHROW_NONNULL_RDONLY1_3_LEAF)
+
+/* stpcpy.  */
+DEF_ATTR_TREE_LIST (ATTR_RETNONNULL_RDONLY2_NOTHROW_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_2,
+		    ATTR_RETNONNULL_NOTHROW_LEAF)
+DEF_ATTR_TREE_LIST (ATTR_RETNONNULL_WRONLY1_RDONLY2_NOTHROW_LEAF,
+		    ATTR_WRITE_ONLY, ATTR_LIST_1,
+		    ATTR_RETNONNULL_RDONLY2_NOTHROW_LEAF)
+
+/* stpncpy.  */
+DEF_ATTR_TREE_LIST (ATTR_RETNONNULL_WRONLY1_3_RDONLY2_NOTHROW_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_2,
+		    ATTR_RETNONNULL_RDONLY2_NOTHROW_LEAF)
+
 /* Construct a tree for the format attribute (and implicitly nonnull).  */
 #define DEF_FORMAT_ATTRIBUTE(TYPE, FA, VALUES)				 \
   DEF_ATTR_TREE_LIST (ATTR_##TYPE##_##VALUES, ATTR_NULL,		 \
diff --git a/gcc/builtins.c b/gcc/builtins.c
index 1fd4b88bcac..d7f88f224e9 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)
 {
@@ -3433,37 +3433,42 @@ 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 = 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);
 	    }
 	  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 = 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);
 	  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 = 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);
 	    }
 	  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 = 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);
+	  if (warned)
+	    TREE_NO_WARNING (exp) = true;
 
 	  /* Return error when an overflow has been detected.  */
 	  return false;
@@ -3486,21 +3491,26 @@ 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 = warning_at (loc, opt,
+				     "%K%qD specified bound %E "
+				     "exceeds maximum object size %E",
+				     exp, func,
+				     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 = warning_at (loc, opt,
+				     "%K%qD specified bound between %E and %E "
+				     "exceeds maximum object size %E",
+				     exp, func,
+				     range[0], range[1], maxobjsize);
+
+	      if (warned)
+		TREE_NO_WARNING (exp) = true;
 
 	      return false;
 	    }
@@ -3510,18 +3520,23 @@ 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 = warning_at (loc, opt,
+				     "%K%qD specified bound %E "
+				     "exceeds destination size %E",
+				     exp, func,
+				     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 = warning_at (loc, opt,
+				     "%K%qD specified bound between %E and %E "
+				     "exceeds destination size %E",
+				     exp, func,
+				     range[0], range[1], dstsize);
+	      if (warned)
+		TREE_NO_WARNING (exp) = true;
+
 	      return false;
 	    }
 	}
@@ -3536,26 +3551,31 @@ 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 = 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);
       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 = warning_at (loc, opt,
+			       "%K%qD reading %E or more bytes from a region "
+			       "of size %E",
+			       exp, func, 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 = 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);
+      if (warned)
+	TREE_NO_WARNING (exp) = true;
+
       return false;
     }
 
diff --git a/gcc/builtins.def b/gcc/builtins.def
index 8bb7027aac7..217e6aa01c8 100644
--- a/gcc/builtins.def
+++ b/gcc/builtins.def
@@ -690,36 +690,36 @@ DEF_C99_COMPL_BUILTIN        (BUILT_IN_CTANL, "ctanl", BT_FN_COMPLEX_LONGDOUBLE_
 /* bcmp, bcopy and bzero have traditionally accepted NULL pointers
    when the length parameter is zero, so don't apply attribute "nonnull".  */
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_BCMP, "bcmp", BT_FN_INT_CONST_PTR_CONST_PTR_SIZE, ATTR_PURE_NOTHROW_LEAF_LIST)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_BCOPY, "bcopy", BT_FN_VOID_CONST_PTR_PTR_SIZE, ATTR_NOTHROW_LEAF_LIST)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_BZERO, "bzero", BT_FN_VOID_PTR_SIZE, ATTR_NOTHROW_LEAF_LIST)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_INDEX, "index", BT_FN_STRING_CONST_STRING_INT, ATTR_PURE_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN        (BUILT_IN_MEMCHR, "memchr", BT_FN_PTR_CONST_PTR_INT_SIZE, ATTR_PURE_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN        (BUILT_IN_MEMCMP, "memcmp", BT_FN_INT_CONST_PTR_CONST_PTR_SIZE, ATTR_PURE_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN	       (BUILT_IN_MEMCPY, "memcpy", BT_FN_PTR_PTR_CONST_PTR_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN	       (BUILT_IN_MEMMOVE, "memmove", BT_FN_PTR_PTR_CONST_PTR_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_MEMPCPY, "mempcpy", BT_FN_PTR_PTR_CONST_PTR_SIZE, ATTR_RETNONNULL_NOTHROW_LEAF)
-DEF_LIB_BUILTIN	       (BUILT_IN_MEMSET, "memset", BT_FN_PTR_PTR_INT_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_RINDEX, "rindex", BT_FN_STRING_CONST_STRING_INT, ATTR_PURE_NOTHROW_NONNULL_LEAF)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_STPCPY, "stpcpy", BT_FN_STRING_STRING_CONST_STRING, ATTR_RETNONNULL_NOTHROW_LEAF)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_STPNCPY, "stpncpy", BT_FN_STRING_STRING_CONST_STRING_SIZE, ATTR_RETNONNULL_NOTHROW_LEAF)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRCASECMP, "strcasecmp", BT_FN_INT_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN        (BUILT_IN_STRCAT, "strcat", BT_FN_STRING_STRING_CONST_STRING, ATTR_RET1_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN        (BUILT_IN_STRCHR, "strchr", BT_FN_STRING_CONST_STRING_INT, ATTR_PURE_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN        (BUILT_IN_STRCMP, "strcmp", BT_FN_INT_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN        (BUILT_IN_STRCPY, "strcpy", BT_FN_STRING_STRING_CONST_STRING, ATTR_RET1_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN        (BUILT_IN_STRCSPN, "strcspn", BT_FN_SIZE_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_LEAF)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRDUP, "strdup", BT_FN_STRING_CONST_STRING, ATTR_MALLOC_WARN_UNUSED_RESULT_NOTHROW_NONNULL_LEAF)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRNDUP, "strndup", BT_FN_STRING_CONST_STRING_SIZE, ATTR_MALLOC_WARN_UNUSED_RESULT_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN        (BUILT_IN_STRLEN, "strlen", BT_FN_SIZE_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_LEAF)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRNCASECMP, "strncasecmp", BT_FN_INT_CONST_STRING_CONST_STRING_SIZE, ATTR_PURE_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN        (BUILT_IN_STRNCAT, "strncat", BT_FN_STRING_STRING_CONST_STRING_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN        (BUILT_IN_STRNCMP, "strncmp", BT_FN_INT_CONST_STRING_CONST_STRING_SIZE, ATTR_PURE_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN        (BUILT_IN_STRNCPY, "strncpy", BT_FN_STRING_STRING_CONST_STRING_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_BCOPY, "bcopy", BT_FN_VOID_CONST_PTR_PTR_SIZE, ATTR_NOTHROW_WRONLY2_3_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_BZERO, "bzero", BT_FN_VOID_PTR_SIZE, ATTR_NOTHROW_WRONLY1_2_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_INDEX, "index", BT_FN_STRING_CONST_STRING_INT, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_LEAF)
+DEF_LIB_BUILTIN        (BUILT_IN_MEMCHR, "memchr", BT_FN_PTR_CONST_PTR_INT_SIZE, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_3_LEAF)
+DEF_LIB_BUILTIN        (BUILT_IN_MEMCMP, "memcmp", BT_FN_INT_CONST_PTR_CONST_PTR_SIZE, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_2_3_LEAF)
+DEF_LIB_BUILTIN       (BUILT_IN_MEMCPY, "memcpy", BT_FN_PTR_PTR_CONST_PTR_SIZE, ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_3_LEAF)
+DEF_LIB_BUILTIN        (BUILT_IN_MEMMOVE, "memmove", BT_FN_PTR_PTR_CONST_PTR_SIZE, ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_3_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_MEMPCPY, "mempcpy", BT_FN_PTR_PTR_CONST_PTR_SIZE, ATTR_RETNONNULL_RDONLY2_3_NOTHROW_LEAF)
+DEF_LIB_BUILTIN        (BUILT_IN_MEMSET, "memset", BT_FN_PTR_PTR_INT_SIZE, ATTR_RET1_NOTHROW_WRONLY1_3_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_RINDEX, "rindex", BT_FN_STRING_CONST_STRING_INT, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_LEAF)
+DEF_EXT_LIB_BUILTIN   (BUILT_IN_STPCPY, "stpcpy", BT_FN_STRING_STRING_CONST_STRING, ATTR_RETNONNULL_WRONLY1_RDONLY2_NOTHROW_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_STPNCPY, "stpncpy", BT_FN_STRING_STRING_CONST_STRING_SIZE, ATTR_RETNONNULL_WRONLY1_3_RDONLY2_NOTHROW_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRCASECMP, "strcasecmp", BT_FN_INT_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF)
+DEF_LIB_BUILTIN       (BUILT_IN_STRCAT, "strcat", BT_FN_STRING_STRING_CONST_STRING, ATTR_RET1_NOTHROW_NONNULL_RDWR1_RDONLY2_LEAF)
+DEF_LIB_BUILTIN       (BUILT_IN_STRCHR, "strchr", BT_FN_STRING_CONST_STRING_INT, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_LEAF)
+DEF_LIB_BUILTIN        (BUILT_IN_STRCMP, "strcmp", BT_FN_INT_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF)
+DEF_LIB_BUILTIN       (BUILT_IN_STRCPY, "strcpy", BT_FN_STRING_STRING_CONST_STRING, ATTR_RET1_NOTHROW_WRONLY1_RDONLY2_LEAF)
+DEF_LIB_BUILTIN        (BUILT_IN_STRCSPN, "strcspn", BT_FN_SIZE_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRDUP, "strdup", BT_FN_STRING_CONST_STRING, ATTR_MALLOC_NOTHROW_NONNULL_RDONLY1_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRNDUP, "strndup", BT_FN_STRING_CONST_STRING_SIZE, ATTR_MALLOC_NOTHROW_NONNULL_RDONLY1_LEAF)
+DEF_LIB_BUILTIN        (BUILT_IN_STRLEN, "strlen", BT_FN_SIZE_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRNCASECMP, "strncasecmp", BT_FN_INT_CONST_STRING_CONST_STRING_SIZE, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF)
+DEF_LIB_BUILTIN        (BUILT_IN_STRNCAT, "strncat", BT_FN_STRING_STRING_CONST_STRING_SIZE, ATTR_RET1_NOTHROW_NONNULL_RDWR1_RDONLY2_LEAF)
+DEF_LIB_BUILTIN        (BUILT_IN_STRNCMP, "strncmp", BT_FN_INT_CONST_STRING_CONST_STRING_SIZE, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF)
+DEF_LIB_BUILTIN        (BUILT_IN_STRNCPY, "strncpy", BT_FN_STRING_STRING_CONST_STRING_SIZE, ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_LEAF)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRNLEN, "strnlen", BT_FN_SIZE_CONST_STRING_SIZE, ATTR_PURE_NOTHROW_NONNULL_LEAF)
 DEF_LIB_BUILTIN        (BUILT_IN_STRPBRK, "strpbrk", BT_FN_STRING_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN        (BUILT_IN_STRRCHR, "strrchr", BT_FN_STRING_CONST_STRING_INT, ATTR_PURE_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN        (BUILT_IN_STRSPN, "strspn", BT_FN_SIZE_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN        (BUILT_IN_STRSTR, "strstr", BT_FN_STRING_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_LEAF)
+DEF_LIB_BUILTIN        (BUILT_IN_STRRCHR, "strrchr", BT_FN_STRING_CONST_STRING_INT, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_LEAF)
+DEF_LIB_BUILTIN        (BUILT_IN_STRSPN, "strspn", BT_FN_SIZE_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF)
+DEF_LIB_BUILTIN        (BUILT_IN_STRSTR, "strstr", BT_FN_STRING_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF)
 
 /* Category: stdio builtins.  */
 DEF_LIB_BUILTIN        (BUILT_IN_FPRINTF, "fprintf", BT_FN_INT_FILEPTR_CONST_STRING_VAR, ATTR_NONNULL_1_FORMAT_PRINTF_2_3)
@@ -865,7 +865,7 @@ DEF_EXT_LIB_BUILTIN    (BUILT_IN_FFSLL, "ffsll", BT_FN_INT_LONGLONG, ATTR_CONST_
 DEF_EXT_LIB_BUILTIN        (BUILT_IN_FORK, "fork", BT_FN_PID, ATTR_NOTHROW_LIST)
 DEF_GCC_BUILTIN        (BUILT_IN_FRAME_ADDRESS, "frame_address", BT_FN_PTR_UINT, ATTR_NULL)
 /* [trans-mem]: Adjust BUILT_IN_TM_FREE if BUILT_IN_FREE is changed.  */
-DEF_LIB_BUILTIN        (BUILT_IN_FREE, "free", BT_FN_VOID_PTR, ATTR_NOTHROW_LEAF_LIST)
+DEF_LIB_BUILTIN        (BUILT_IN_FREE, "free", BT_FN_VOID_PTR, ATTR_NOTHROW_WRONLY1_LEAF)
 DEF_GCC_BUILTIN        (BUILT_IN_FROB_RETURN_ADDR, "frob_return_addr", BT_FN_PTR_PTR, ATTR_NULL)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_GETTEXT, "gettext", BT_FN_STRING_CONST_STRING, ATTR_FORMAT_ARG_1)
 DEF_C99_BUILTIN        (BUILT_IN_IMAXABS, "imaxabs", BT_FN_INTMAX_INTMAX, ATTR_CONST_NOTHROW_LEAF_LIST)
@@ -962,16 +962,16 @@ DEF_BUILTIN_STUB (BUILT_IN_STRNCMP_EQ, "__builtin_strncmp_eq")
 
 /* Object size checking builtins.  */
 DEF_GCC_BUILTIN	       (BUILT_IN_OBJECT_SIZE, "object_size", BT_FN_SIZE_CONST_PTR_INT, ATTR_PURE_NOTHROW_LEAF_LIST)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_MEMCPY_CHK, "__memcpy_chk", BT_FN_PTR_PTR_CONST_PTR_SIZE_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_MEMMOVE_CHK, "__memmove_chk", BT_FN_PTR_PTR_CONST_PTR_SIZE_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_MEMCPY_CHK, "__memcpy_chk", BT_FN_PTR_PTR_CONST_PTR_SIZE_SIZE, ATTR_RET1_NOTHROW_WRONLY1_3_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_MEMMOVE_CHK, "__memmove_chk", BT_FN_PTR_PTR_CONST_PTR_SIZE_SIZE, ATTR_RET1_NOTHROW_WRONLY1_3_LEAF)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_MEMPCPY_CHK, "__mempcpy_chk", BT_FN_PTR_PTR_CONST_PTR_SIZE_SIZE, ATTR_RETNONNULL_NOTHROW_LEAF)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_MEMSET_CHK, "__memset_chk", BT_FN_PTR_PTR_INT_SIZE_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
+DEF_EXT_LIB_BUILTIN   (BUILT_IN_MEMSET_CHK, "__memset_chk", BT_FN_PTR_PTR_INT_SIZE_SIZE, ATTR_RET1_NOTHROW_WRONLY1_3_LEAF)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_STPCPY_CHK, "__stpcpy_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE, ATTR_RETNONNULL_NOTHROW_LEAF)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_STPNCPY_CHK, "__stpncpy_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE_SIZE, ATTR_RETNONNULL_NOTHROW_LEAF)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRCAT_CHK, "__strcat_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRCPY_CHK, "__strcpy_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRCPY_CHK, "__strcpy_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE, ATTR_RET1_NOTHROW_WRONLY1_LEAF)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRNCAT_CHK, "__strncat_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRNCPY_CHK, "__strncpy_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRNCPY_CHK, "__strncpy_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE_SIZE, ATTR_RET1_NOTHROW_WRONLY1_3_LEAF)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_SNPRINTF_CHK, "__snprintf_chk", BT_FN_INT_STRING_SIZE_INT_SIZE_CONST_STRING_VAR, ATTR_FORMAT_PRINTF_NOTHROW_5_6)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_SPRINTF_CHK, "__sprintf_chk", BT_FN_INT_STRING_INT_SIZE_CONST_STRING_VAR, ATTR_NOTHROW_NONNULL_1_FORMAT_PRINTF_4_5)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_VSNPRINTF_CHK, "__vsnprintf_chk", BT_FN_INT_STRING_SIZE_INT_SIZE_CONST_STRING_VALIST_ARG, ATTR_FORMAT_PRINTF_NOTHROW_5_0)
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 6500b998321..e699f429618 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -125,6 +125,10 @@ 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_read_only_attribute (tree *, tree, tree, int, bool *);
+static tree handle_read_write_attribute (tree *, tree, tree, int, bool *);
+static tree handle_write_only_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 *);
@@ -468,6 +472,12 @@ 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 },
+  { "read_only",              0, 2, false, true, true, false,
+			      handle_read_only_attribute, NULL },
+  { "write_only",             0, 2, false, true, true, false,
+			      handle_write_only_attribute, NULL },
+  { "read_write",             0, 2, false, true, true, false,
+			      handle_read_write_attribute, NULL },
   { NULL,                     0, 0, false, false, false, false, NULL, NULL }
 };
 
@@ -3775,6 +3785,228 @@ 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 ( ; ; ++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;
+}
+
+/* Handle attributes read_only, write_only, and read_write.  */
+
+static tree
+handle_rdwr_attributes (bool writeable, tree *node, tree name,
+			tree operands, int ARG_UNUSED (flags),
+			bool *no_add_attrs)
+{
+  tree type = *node;
+  tree attrs = TYPE_ATTRIBUTES (type);
+
+  /* If no operands are specified, all non-const pointer arguments are
+     treated as write-only.  Verify a full prototype is given so that
+     the arguments will have the correct types when we actually check
+     them later.  Avoid diagnosing type-generic built-ins since those
+     have no prototype.  */
+  if (!operands
+      && !prototype_p (type)
+      && (!attrs || !lookup_attribute ("type generic", attrs)))
+    {
+      error ("attribute %qE without arguments on a non-prototype", name);
+      *no_add_attrs = true;
+      return NULL_TREE;
+    }
+
+  /* Attribute operands have been specified.  Verify that each operand
+     value references a non-const pointer argument to the function.  */
+  tree idxnodes[2] = { NULL_TREE, NULL_TREE };
+  tree argtypes[2] = { NULL_TREE, NULL_TREE };
+  unsigned HOST_WIDE_INT idxs[2] = { 0, 0 };
+
+  /* Number of function formal arguments (used in diagnostics).  */
+  unsigned argcount = 0;
+  /* Number of attribute operands.  */
+  unsigned opcount = 0;
+
+  for (unsigned i = 0; i != 2; ++i, operands = TREE_CHAIN (operands), ++opcount)
+    {
+      if (!operands)
+	break;
+
+      idxnodes[i] = TREE_VALUE (operands);
+
+      if (TREE_CODE (idxnodes[i]) != IDENTIFIER_NODE
+	  && TREE_CODE (idxnodes[i]) != FUNCTION_DECL)
+	idxnodes[i] = default_conversion (idxnodes[i]);
+
+      if (!get_attribute_operand (idxnodes[i], idxs + i))
+	{
+	  *no_add_attrs = true;
+	  idxs[i] = i + 1;
+	}
+
+      argtypes[i] = get_argument_type (type, idxs[i], &argcount);
+    }
+
+  if (*no_add_attrs)
+    {
+      /* Dignose an invalid operand and bail.  */
+      if (idxnodes[1])
+	error ("attribute %<%E(%E, %E)%> invalid operand %wu",
+	       name, idxnodes[0], idxnodes[1], idxs[0] ? idxs[0] : idxs[1]);
+      else
+	error ("attribute %<%E(%E)%> invalid operand",
+	       name, idxnodes[0]);
+
+      return NULL_TREE;
+    }
+
+  if (!argtypes[0] || (idxnodes[1] && !argtypes[1]))
+    {
+      if (idxnodes[1])
+	error ("attribute %<%E(%E, %E)%> operand exceeds number "
+	       "of arguments %u",
+	       name, idxnodes[0], idxnodes[1], argcount);
+      else if (idxnodes[0])
+	error ("attribute %<%E(%E)%> operand exceeds number of arguments %u",
+	       name, idxnodes[0], argcount);
+      else if (!opcount)
+	{
+	  /* FIXME: reject empty attribute write_only on function
+	     declarations with no non-const object pointer arguments.  */
+	  tree arg;
+	  function_args_iterator it;
+	  FOREACH_FUNCTION_ARGS (*node, arg, it)
+	    if (POINTER_TYPE_P (arg))
+	      return NULL_TREE;
+
+	  error ("attribute %<%E%> on a function with no pointer arguments",
+		 name);
+	  *no_add_attrs = true;
+	  return NULL_TREE;
+	}
+      else
+	error ("attribute %<%E%> invalid operand",
+	       name);
+
+      *no_add_attrs = true;
+      return NULL_TREE;
+    }
+
+  if (TREE_CODE (argtypes[0]) != POINTER_TYPE)
+    {
+      if (idxnodes[1])
+	error ("attribute %<%E(%E, %E)%> first operand references "
+	       "non-pointer argument type %qT",
+	       name, idxnodes[0], idxnodes[1], argtypes[0]);
+      else
+	error ("attribute %<%E(%E)%> references non-pointer argument "
+	       "type %qT",
+	       name, idxnodes[0], argtypes[0]);
+      *no_add_attrs = true;
+      return NULL_TREE;
+    }
+
+  if (writeable)
+    {
+      /* A read_write and write_only attributes must reference non-const
+	 arguments.  */
+      if (TYPE_READONLY (TREE_TYPE (argtypes[0])))
+	{
+	  if (idxnodes[1])
+	    error ("attribute %<%E(%E, %E)%> first operand references "
+		   "%qs-qualified argument type %qT",
+		   name, idxnodes[0], idxnodes[1], "const", argtypes[0]);
+	  else
+	    error ("attribute %<%E(%E)%> operand references %qs-qualified "
+		   "argument type %qT",
+		   name, idxnodes[0], "const", argtypes[0]);
+	  *no_add_attrs = true;
+	  return NULL_TREE;
+	}
+    }
+  else if (!TYPE_READONLY (TREE_TYPE (argtypes[0])))
+    {
+      /* A read_only attributes should reference const-qualified arguments
+	 but it's not an error if one doesn't.  */
+      if (idxnodes[1])
+	warning (OPT_Wattributes,
+		 "attribute %<%E(%E, %E)%> first operand references "
+		 "non-%qs argument type %qT",
+		 name, idxnodes[0], idxnodes[1], "const", argtypes[0]);
+      else
+	warning (OPT_Wattributes,
+		 "attribute %<%E(%E)%> operand references non-%qs "
+		 "argument type %qT",
+		 name, idxnodes[0], "const", argtypes[0]);
+    }
+
+  if (argtypes[1] && !INTEGRAL_TYPE_P (argtypes[1]))
+    {
+      if (idxnodes[1])
+	error ("attribute %<%E(%E, %E)%> second operand references "
+	       "non-integer argument type %qT",
+	       name, idxnodes[0], idxnodes[1], argtypes[1]);
+      else
+	error ("attribute %<%E(%E)%> second operand references "
+	       "non-integer argument type %qT",
+	       name, idxnodes[0], argtypes[1]);
+      *no_add_attrs = true;
+      return NULL_TREE;
+    }
+
+  return NULL_TREE;
+}
+
+/* Handle attribute read_only ([ptrarg [, sizarg]]).  */
+
+static tree
+handle_read_only_attribute (tree *node, tree name, tree operands, int flags,
+			    bool *no_add_attrs)
+{
+  return handle_rdwr_attributes (false, node, name, operands, flags,
+				 no_add_attrs);
+}
+
+/* Handle attribute write_only ([ptrarg [, sizarg]]).  */
+
+static tree
+handle_write_only_attribute (tree *node, tree name, tree operands, int flags,
+			     bool *no_add_attrs)
+{
+  return handle_rdwr_attributes (true, node, name, operands, flags,
+				 no_add_attrs);
+}
+
+/* Handle attribute read_write ([ptrarg [, sizarg]]).  */
+
+static tree
+handle_read_write_attribute (tree *node, tree name,
+			     tree operands, int flags, bool *no_add_attrs)
+{
+  return handle_rdwr_attributes (true, node, name, operands, flags,
+				 no_add_attrs);
+}
+
 /* Handle a "nothrow" attribute; arguments as in
    struct attribute_spec.handler.  */
 
@@ -4215,7 +4447,7 @@ validate_attribute (location_t atloc, tree oper, tree attr)
    consider just its type.  */
 
 bool
-has_attribute (location_t atloc, tree t, tree attr, tree (*convert)(tree))
+has_attribute (location_t atloc, tree t, tree attr, tree (*convert)(tree, bool))
 {
   if (!attr || !t || t == error_mark_node)
     return false;
@@ -4348,8 +4580,8 @@ has_attribute (location_t atloc, tree t, tree attr, tree (*convert)(tree))
 		    return false;
 
 		  /* Convert to make them equality-comparable.  */
-		  v1 = convert (v1);
-		  v2 = convert (v2);
+		  v1 = convert (v1, false);
+		  v2 = convert (v2, false);
 
 		  /* A positive value indicates equality, negative means
 		     "don't know."  */
@@ -4369,7 +4601,7 @@ has_attribute (location_t atloc, tree t, tree attr, tree (*convert)(tree))
 	    {
 	      if (tree arg = TREE_VALUE (attr))
 		{
-		  arg = convert (TREE_VALUE (arg));
+		  arg = convert (TREE_VALUE (arg), false);
 		  if (!tree_fits_uhwi_p (arg))
 		    /* Invalid argument.  */;
 		  else if (expr && DECL_P (expr)
@@ -4416,7 +4648,7 @@ has_attribute (location_t atloc, tree t, tree attr, tree (*convert)(tree))
 	      if (tree arg = TREE_VALUE (attr))
 		{
 		  /* Compare the vector size argument for equality.  */
-		  arg = convert (TREE_VALUE (arg));
+		  arg = convert (TREE_VALUE (arg), false);
 		  return tree_int_cst_equal (arg, TYPE_SIZE_UNIT (type)) == 1;
 		}
 	      else
@@ -4426,7 +4658,7 @@ has_attribute (location_t atloc, tree t, tree attr, tree (*convert)(tree))
 	    {
 	      if (tree arg = TREE_VALUE (attr))
 		{
-		  arg = convert (TREE_VALUE (arg));
+		  arg = convert (TREE_VALUE (arg), false);
 		  if (expr && DECL_P (expr))
 		    found_match = (DECL_WARN_IF_NOT_ALIGN (expr)
 				   == tree_to_uhwi (arg) * BITS_PER_UNIT);
diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c
index 3756219e5cf..259d7f44008 100644
--- a/gcc/c-family/c-common.c
+++ b/gcc/c-family/c-common.c
@@ -5479,7 +5479,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);
 
@@ -5514,11 +5514,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 a9f4d0c5c11..eb8ac57aba4 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -869,7 +869,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)
@@ -969,13 +969,14 @@ extern tree build_real_imag_expr (location_t, enum tree_code, tree);
    a variant of the C language.  They are used in c-common.c.  */
 
 extern tree build_unary_op (location_t, enum tree_code, tree, bool);
-extern tree build_binary_op (location_t, enum tree_code, tree, tree, bool);
+extern tree build_binary_op (location_t, enum tree_code, tree, tree, bool,
+			     bool = true);
 extern tree perform_integral_promotions (tree);
 
 /* These functions must be defined by each front-end which implements
    a variant of the C language.  They are used by port files.  */
 
-extern tree default_conversion (tree);
+extern tree default_conversion (tree, bool = true);
 
 /* Given two integer or real types, return the type for their sum.
    Given two compatible ANSI C types, returns the merged type.  */
@@ -1376,7 +1377,7 @@ extern void maybe_suggest_missing_token_insertion (rich_location *richloc,
 						   location_t prev_token_loc);
 extern tree braced_lists_to_strings (tree, tree);
 
-extern bool has_attribute (location_t, tree, tree, tree (*)(tree));
+extern bool has_attribute (location_t, tree, tree, tree (*)(tree, bool));
 
 #if CHECKING_P
 namespace selftest {
diff --git a/gcc/c/c-parser.c b/gcc/c/c-parser.c
index 6070502deb5..1f97a259951 100644
--- a/gcc/c/c-parser.c
+++ b/gcc/c/c-parser.c
@@ -187,6 +187,14 @@ struct GTY(()) c_parser {
   BOOL_BITFIELD in_if_block : 1;
   /* True if we want to lex an untranslated string.  */
   BOOL_BITFIELD lex_untranslated_string : 1;
+  /* True if taking the address of a DECL should not set its DECL_READ_P
+     flag.  */
+  BOOL_BITFIELD no_set_read : 1;
+  /* True when parsing an initializer expression.  */
+  BOOL_BITFIELD in_init : 1;
+  /* True when parsing a non-empty expression list such as a list of
+     arguments in a function call.  */
+  BOOL_BITFIELD in_expr_list : 1;
 
   /* Objective-C specific parser/lexer information.  */
 
@@ -1447,7 +1455,8 @@ static tree c_parser_transaction_cancel (c_parser *);
 static struct c_expr c_parser_expression (c_parser *);
 static struct c_expr c_parser_expression_conv (c_parser *);
 static vec<tree, va_gc> *c_parser_expr_list (c_parser *, bool, bool,
-					     vec<tree, va_gc> **, location_t *,
+					     vec<tree, va_gc> **, tree,
+					     location_t *,
 					     tree *, vec<location_t> *,
 					     unsigned int * = NULL);
 static struct c_expr c_parser_has_attribute_expression (c_parser *);
@@ -4431,7 +4440,7 @@ c_parser_attribute (c_parser *parser, tree attrs,
 	  tree tree_list;
 	  c_parser_consume_token (parser);
 	  expr_list = c_parser_expr_list (parser, false, true,
-					  NULL, NULL, NULL, NULL);
+					  NULL, NULL, NULL, NULL, NULL);
 	  tree_list = build_tree_list_vec (expr_list);
 	  attr_args = tree_cons (NULL_TREE, arg1, tree_list);
 	  release_tree_vector (expr_list);
@@ -4444,7 +4453,7 @@ c_parser_attribute (c_parser *parser, tree attrs,
       else
 	{
 	  expr_list = c_parser_expr_list (parser, false, true,
-					  NULL, NULL, NULL, NULL);
+					  NULL, NULL, NULL, NULL, NULL);
 	  attr_args = build_tree_list_vec (expr_list);
 	  release_tree_vector (expr_list);
 	}
@@ -4624,12 +4633,17 @@ c_parser_initializer (c_parser *parser)
     return c_parser_braced_init (parser, NULL_TREE, false, NULL);
   else
     {
+      const bool saved_in_init = parser->in_init;
+      parser->in_init = true;
+
       struct c_expr ret;
       location_t loc = c_parser_peek_token (parser)->location;
       ret = c_parser_expr_no_commas (parser, NULL);
       if (TREE_CODE (ret.value) != STRING_CST
 	  && TREE_CODE (ret.value) != COMPOUND_LITERAL_EXPR)
 	ret = convert_lvalue_to_rvalue (loc, ret, true, true);
+
+      parser->in_init = saved_in_init;
       return ret;
     }
 }
@@ -7002,6 +7016,9 @@ c_parser_binary_expression (c_parser *parser, struct c_expr *after,
     tree sizeof_arg;
   } stack[NUM_PRECS];
   int sp;
+
+  bool read_p = !parser->no_set_read;
+
   /* Location of the binary operator.  */
   location_t binary_loc = UNKNOWN_LOCATION;  /* Quiet warning.  */
 #define POP								      \
@@ -7050,10 +7067,10 @@ c_parser_binary_expression (c_parser *parser, struct c_expr *after,
       }									      \
     stack[sp - 1].expr							      \
       = convert_lvalue_to_rvalue (stack[sp - 1].loc,			      \
-				  stack[sp - 1].expr, true, true);	      \
+				  stack[sp - 1].expr, true, read_p);	      \
     stack[sp].expr							      \
       = convert_lvalue_to_rvalue (stack[sp].loc,			      \
-				  stack[sp].expr, true, true);		      \
+				  stack[sp].expr, true, read_p);	      \
     if (__builtin_expect (omp_atomic_lhs != NULL_TREE, 0) && sp == 1	      \
 	&& c_parser_peek_token (parser)->type == CPP_SEMICOLON		      \
 	&& ((1 << stack[sp].prec)					      \
@@ -7071,7 +7088,8 @@ c_parser_binary_expression (c_parser *parser, struct c_expr *after,
       stack[sp - 1].expr = parser_build_binary_op (stack[sp].loc,	      \
 						   stack[sp].op,	      \
 						   stack[sp - 1].expr,	      \
-						   stack[sp].expr);	      \
+						   stack[sp].expr,	      \
+						   read_p);		      \
     sp--;								      \
   } while (0)
   gcc_assert (!after || c_dialect_objc ());
@@ -7176,7 +7194,7 @@ c_parser_binary_expression (c_parser *parser, struct c_expr *after,
 	  src_range = stack[sp].expr.src_range;
 	  stack[sp].expr
 	    = convert_lvalue_to_rvalue (stack[sp].loc,
-					stack[sp].expr, true, true);
+					stack[sp].expr, true, read_p);
 	  stack[sp].expr.value = c_objc_common_truthvalue_conversion
 	    (stack[sp].loc, default_conversion (stack[sp].expr.value));
 	  c_inhibit_evaluation_warnings += (stack[sp].expr.value
@@ -7187,7 +7205,7 @@ c_parser_binary_expression (c_parser *parser, struct c_expr *after,
 	  src_range = stack[sp].expr.src_range;
 	  stack[sp].expr
 	    = convert_lvalue_to_rvalue (stack[sp].loc,
-					stack[sp].expr, true, true);
+					stack[sp].expr, true, read_p);
 	  stack[sp].expr.value = c_objc_common_truthvalue_conversion
 	    (stack[sp].loc, default_conversion (stack[sp].expr.value));
 	  c_inhibit_evaluation_warnings += (stack[sp].expr.value
@@ -7339,7 +7357,10 @@ c_parser_unary_expression (c_parser *parser)
     case CPP_AND:
       c_parser_consume_token (parser);
       op = c_parser_cast_expression (parser, NULL);
-      mark_exp_read (op.value);
+      /* Avoid setting DECL_READ_P of a referenced object when its
+	 address is being passed to a write-only function parameter.  */
+      if (!parser->no_set_read)
+	mark_exp_read (op.value);
       return parser_build_unary_op (op_loc, ADDR_EXPR, op);
     case CPP_MULT:
       {
@@ -7502,7 +7523,10 @@ c_parser_sizeof_expression (c_parser *parser)
     sizeof_expr:
       c_inhibit_evaluation_warnings--;
       in_sizeof--;
-      mark_exp_read (expr.value);
+      /* Avoid setting DECL_READ_P of a sizeof operand when the result
+	 is passed to a function with a write-only function parameter.  */
+      if (!parser->in_expr_list)
+	mark_exp_read (expr.value);
       if (TREE_CODE (expr.value) == COMPONENT_REF
 	  && DECL_C_BIT_FIELD (TREE_OPERAND (expr.value, 1)))
 	error_at (expr_loc, "%<sizeof%> applied to a bit-field");
@@ -9618,8 +9642,10 @@ c_parser_postfix_expression_after_primary (c_parser *parser,
 	    exprlist = NULL;
 	  else
 	    exprlist = c_parser_expr_list (parser, true, false, &origtypes,
+					   expr.value,
 					   sizeof_arg_loc, sizeof_arg,
 					   &arg_loc, &literal_zero_mask);
+
 	  c_parser_skip_until_found (parser, CPP_CLOSE_PAREN,
 				     "expected %<)%>");
 	  orig_expr = expr;
@@ -9851,6 +9877,32 @@ c_parser_check_literal_zero (c_parser *parser, unsigned *literal_zero_mask,
     }
 }
 
+/* Return true if expression number IDX and type ARGTYPE eclared with
+   the write-only attribute in ATTRS is itself declared write-only.
+   ATTRS may be null.  */
+
+static bool
+is_write_only_p (tree attrs, tree argtype, unsigned idx)
+{
+  if (!attrs)
+    return false;
+
+  if (!POINTER_TYPE_P (argtype))
+    return false;
+
+  if (TYPE_READONLY (TREE_TYPE (argtype)))
+    return false;
+
+  for (tree a = attrs; a; a = TREE_CHAIN (a))
+    {
+      unsigned HOST_WIDE_INT wronlyidx;
+      if (!get_attribute_operand (a, &wronlyidx) || wronlyidx == idx)
+	return true;
+    }
+  return false;
+}
+
+
 /* Parse a non-empty list of expressions.  If CONVERT_P, convert
    functions and arrays to pointers and lvalues to rvalues.  If
    FOLD_P, fold the expressions.  If LOCATIONS is non-NULL, save the
@@ -9863,7 +9915,7 @@ c_parser_check_literal_zero (c_parser *parser, unsigned *literal_zero_mask,
 
 static vec<tree, va_gc> *
 c_parser_expr_list (c_parser *parser, bool convert_p, bool fold_p,
-		    vec<tree, va_gc> **p_orig_types,
+		    vec<tree, va_gc> **p_orig_types, tree func,
 		    location_t *sizeof_arg_loc, tree *sizeof_arg,
 		    vec<location_t> *locations,
 		    unsigned int *literal_zero_mask)
@@ -9881,9 +9933,44 @@ c_parser_expr_list (c_parser *parser, bool convert_p, bool fold_p,
 
   if (literal_zero_mask)
     c_parser_check_literal_zero (parser, literal_zero_mask, 0);
-  expr = c_parser_expr_no_commas (parser, NULL);
+
+  tree functype = func ? TREE_TYPE (func) : NULL_TREE;
+  if (functype)
+    {
+      while (POINTER_TYPE_P (functype))
+	functype = TREE_TYPE (functype);
+    }
+
+  tree attrs = (functype && functype != error_mark_node
+		? lookup_attribute ("write_only", TYPE_ATTRIBUTES (functype))
+		: NULL_TREE);
+
+  tree argtypes = attrs ? TYPE_ARG_TYPES (functype) : NULL_TREE;
+
+  /* Determine if the first function parameter in the list is declared
+     write-only and only mark the corresponding argument DECL_READ_P()
+     if it either isn't write-only or doesn't reference the address of
+     a declared object.  */
+  bool wronly = (argtypes
+		 && !parser->in_init
+		 && is_write_only_p (attrs, TREE_VALUE (argtypes), idx + 1));
+
+  {
+    /* Avoid setting the read bit for write-only decls.  */
+    bool save_set_read = parser->no_set_read;
+    parser->no_set_read = wronly;
+    expr = c_parser_expr_no_commas (parser, NULL);
+    parser->no_set_read = save_set_read;
+  }
+
   if (convert_p)
-    expr = convert_lvalue_to_rvalue (expr.get_location (), expr, true, true);
+    {
+      /* Allow DECL_READ_P to be set if the expression is a pointer.  */
+      wronly &= !DECL_P (expr.value) || !POINTER_TYPE_P (TREE_TYPE (expr.value));
+      expr = convert_lvalue_to_rvalue (expr.get_location (), expr, true,
+				       !wronly);
+    }
+
   if (fold_p)
     expr.value = c_fully_fold (expr.value, false, NULL);
   ret->quick_push (expr.value);
@@ -9902,10 +9989,41 @@ c_parser_expr_list (c_parser *parser, bool convert_p, bool fold_p,
       c_parser_consume_token (parser);
       if (literal_zero_mask)
 	c_parser_check_literal_zero (parser, literal_zero_mask, idx + 1);
-      expr = c_parser_expr_no_commas (parser, NULL);
+
+      /* Determine if the next function parameter in the list is declared
+	 write-only and only mark the corresponding argument DECL_READ_P()
+	 if it either isn't write-only or doesn't reference the address of
+	 a declared object.  */
+      if (argtypes && attrs)
+	{
+	  argtypes = TREE_CHAIN (argtypes);
+	  wronly = (!parser->in_init
+		    && is_write_only_p (attrs, TREE_VALUE (argtypes), idx + 2));
+	}
+      else
+	wronly = false;
+
+      {
+	/* Avoid setting the read bit for write-only decls.  */
+	bool save_set_read = parser->no_set_read;
+	parser->no_set_read = wronly;
+	/* Indicate a an expression list is being parsed.  */
+	bool save_in_expr_list = parser->in_expr_list;
+	parser->in_expr_list = true;
+	expr = c_parser_expr_no_commas (parser, NULL);
+	/* Restore both bits.  */
+	parser->no_set_read = save_set_read;
+	parser->in_expr_list = save_in_expr_list;
+      }
+
       if (convert_p)
-	expr = convert_lvalue_to_rvalue (expr.get_location (), expr, true,
-					 true);
+	{
+	  wronly &= (!DECL_P (expr.value)
+		     || !POINTER_TYPE_P (TREE_TYPE (expr.value)));
+	  expr = convert_lvalue_to_rvalue (expr.get_location (), expr, true,
+					   !wronly);
+	}
+
       if (fold_p)
 	expr.value = c_fully_fold (expr.value, false, NULL);
       vec_safe_push (ret, expr.value);
@@ -9923,6 +10041,7 @@ c_parser_expr_list (c_parser *parser, bool convert_p, bool fold_p,
     }
   if (orig_types)
     *p_orig_types = orig_types;
+
   return ret;
 }
 \f
@@ -11016,8 +11135,8 @@ static tree
 c_parser_objc_keywordexpr (c_parser *parser)
 {
   tree ret;
-  vec<tree, va_gc> *expr_list = c_parser_expr_list (parser, true, true,
-						NULL, NULL, NULL, NULL);
+  vec<tree, va_gc> *expr_list = c_parser_expr_list (parser, true, true, NULL,
+						    NULL_TREE, NULL, NULL, NULL);
   if (vec_safe_length (expr_list) == 1)
     {
       /* Just return the expression, remove a level of
@@ -11948,7 +12067,7 @@ c_parser_oacc_wait_list (c_parser *parser, location_t clause_loc, tree list)
   if (!parens.require_open (parser))
     return list;
 
-  args = c_parser_expr_list (parser, false, true, NULL, NULL, NULL, NULL);
+  args = c_parser_expr_list (parser, false, true, NULL, NULL, NULL, NULL, NULL);
   args_tree = build_tree_list_vec (args);
 
   for (t = args_tree; t; t = TREE_CHAIN (t))
diff --git a/gcc/c/c-tree.h b/gcc/c/c-tree.h
index dae2979d482..eda18618452 100644
--- a/gcc/c/c-tree.h
+++ b/gcc/c/c-tree.h
@@ -665,7 +665,7 @@ extern struct c_expr parser_build_unary_op (location_t, enum tree_code,
     					    struct c_expr);
 extern struct c_expr parser_build_binary_op (location_t,
     					     enum tree_code, struct c_expr,
-					     struct c_expr);
+					     struct c_expr, bool = true);
 extern tree build_conditional_expr (location_t, tree, bool, tree, tree,
 				    location_t, tree, tree, location_t);
 extern tree build_compound_expr (location_t, tree, tree);
diff --git a/gcc/c/c-typeck.c b/gcc/c/c-typeck.c
index d4e12eb93d1..2e439c43b91 100644
--- a/gcc/c/c-typeck.c
+++ b/gcc/c/c-typeck.c
@@ -2146,14 +2146,30 @@ perform_integral_promotions (tree exp)
    In addition, manifest constants symbols are replaced by their values.  */
 
 tree
-default_conversion (tree exp)
+default_conversion (tree exp, bool read_p /* = true */)
 {
   tree orig_exp;
   tree type = TREE_TYPE (exp);
   enum tree_code code = TREE_CODE (type);
   tree promoted_type;
 
-  mark_exp_read (exp);
+  if (!read_p)
+    {
+      /* Set READ_P for expressions that don't refer to declared objects.  */
+      tree tmp = exp;
+      STRIP_NOPS (tmp);
+      if (TREE_CODE (tmp) == ADDR_EXPR)
+	tmp = TREE_OPERAND (tmp, 0);
+
+      while (handled_component_p (tmp))
+	tmp = TREE_OPERAND (tmp, 0);
+
+      if (!DECL_P (tmp))
+	read_p = true;
+    }
+
+  if (read_p)
+    mark_exp_read (exp);
 
   /* Functions and arrays have been converted during parsing.  */
   gcc_assert (code != FUNCTION_TYPE);
@@ -3718,7 +3734,8 @@ char_type_p (tree type)
 
 struct c_expr
 parser_build_binary_op (location_t location, enum tree_code code,
-			struct c_expr arg1, struct c_expr arg2)
+			struct c_expr arg1, struct c_expr arg2,
+			bool read_p /* = true */)
 {
   struct c_expr result;
 
@@ -3732,7 +3749,7 @@ parser_build_binary_op (location_t location, enum tree_code code,
                 : TREE_TYPE (arg2.value));
 
   result.value = build_binary_op (location, code,
-				  arg1.value, arg2.value, true);
+				  arg1.value, arg2.value, true, read_p);
   result.original_code = code;
   result.original_type = NULL;
 
@@ -11354,7 +11371,7 @@ build_vec_cmp (tree_code code, tree type,
 
 tree
 build_binary_op (location_t location, enum tree_code code,
-		 tree orig_op0, tree orig_op1, bool convert_p)
+		 tree orig_op0, tree orig_op1, bool convert_p, bool read_p)
 {
   tree type0, type1, orig_type0, orig_type1;
   tree eptype;
@@ -11459,8 +11476,8 @@ build_binary_op (location_t location, enum tree_code code,
   if (convert_p
       && VECTOR_TYPE_P (TREE_TYPE (op0)) == VECTOR_TYPE_P (TREE_TYPE (op1)))
     {
-      op0 = default_conversion (op0);
-      op1 = default_conversion (op1);
+      op0 = default_conversion (op0, read_p);
+      op1 = default_conversion (op1, read_p);
     }
 
   orig_type0 = type0 = TREE_TYPE (op0);
diff --git a/gcc/calls.c b/gcc/calls.c
index 51ad55f15a9..f8463bf98ba 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"
@@ -1862,6 +1864,109 @@ 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 read_only,
+   write_only, or read_write operand and the read/write specification.  */
+typedef hash_map<rdwr_access_hash, attr_access> rdwr_map;
+
+/* Initialize a mapping for a call to function FNDECL declared with
+   attributes read_only, write_only, or read_write.  Each attribute
+   operand inserts one entry into the mapping with the operand number
+   as the key.  */
+
+static void
+init_attr_rdwr_indices (rdwr_map *rwm, tree fndecl)
+{
+  if (!fndecl)
+    return;
+
+  /* Iterate over arguments to functions declared with attribute
+     read_only or read_write and diagnose those that are not
+     initialized.  */
+  const attr_access::Kind access_kinds[] = {
+    attr_access::read_only, attr_access::read_write, attr_access::write_only
+  };
+
+  for (unsigned i = 0; i != sizeof access_kinds / sizeof *access_kinds; ++i)
+    {
+      attr_access acc = { };
+      acc.kind = access_kinds[i];
+      while (get_attr_access_ptr_and_size (TREE_TYPE (fndecl), &acc))
+	{
+	  rwm->put (acc.ptrarg, acc);
+	  if (acc.ptrarg != UINT_MAX)
+	    rwm->put (acc.sizarg, acc);
+	}
+    }
+}
+
+/* Iterate over read-only, read-write, and write-only arguments
+   and diagnose past-the-end accesses in the function call EXP.  */
+
+static void
+maybe_warn_rdwr_sizes (rdwr_map *rwm, tree exp)
+{
+  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
+	 first and (optionally) second operands.  When both operands 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.  */
+      int ptridx = access.second.ptrarg;
+      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;
+
+      /* If only one attribute operand was specified the size isn't
+	 available and so continue to the next one.  */
+
+      /* FIXME: determine the size using some heuristic -- e.g., for
+	 complete types, by assuming it's equal to the size of the type
+	 the pointer points to.  */
+      if (sizidx == -1)
+	continue;
+
+      tree ptr = access.second.ptr;
+      tree size = rwm->get (sizidx)->size;
+      tree objsize = compute_objsize (ptr, 0);
+
+      tree srcsize;
+      if (access.second.kind == 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.kind == 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;
+	    }
+	}
+
+      check_access (exp, NULL_TREE, NULL_TREE, size, /*maxread=*/ NULL_TREE,
+		    srcsize, objsize);
+    }
+}
+
 /* Fill in ARGS_SIZE and ARGS array based on the parameters found in
    CALL_EXPR EXP.
 
@@ -1978,6 +2083,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, fndecl);
+
   /* I counts args in order (to be) pushed; ARGPOS counts in order written.  */
   for (argpos = 0; argpos < num_actuals; i--, argpos++)
     {
@@ -2218,6 +2328,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])
@@ -2230,6 +2356,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/cp/typeck.c b/gcc/cp/typeck.c
index f427c4f4d3e..917e8c3c917 100644
--- a/gcc/cp/typeck.c
+++ b/gcc/cp/typeck.c
@@ -2191,7 +2191,7 @@ cp_default_conversion (tree exp, tsubst_flags_t complain)
 /* C version.  */
 
 tree
-default_conversion (tree exp)
+default_conversion (tree exp, bool /*read_p*/)
 {
   return cp_default_conversion (exp, tf_warning_or_error);
 }
@@ -4309,7 +4309,7 @@ enum_cast_to_int (tree op)
 /* For the c-common bits.  */
 tree
 build_binary_op (location_t location, enum tree_code code, tree op0, tree op1,
-		 bool /*convert_p*/)
+		 bool /*convert_p*/, bool /*read_p*/)
 {
   return cp_build_binary_op (location, code, op0, op1, tf_warning_or_error);
 }
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 64fccfe9b87..11ff70a8e91 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -3850,6 +3850,86 @@ At present, a declaration to which @code{weakref} is attached can
 only be @code{static}.
 
 
+@item read_only
+@itemx read_only
+@itemx read_only (@var{arg-index})
+@itemx read_only (@var{ref-index}, @var{size-index})
+@cindex @code{read_only} function attribute
+The @code{read_only} attribute specifies that a function whose by-reference
+arguments of @code{const}-qualified types denoted by @var{ref-index} (or
+all such arguments if no @var{ref-index} is specified) are only used to read
+from the objects referenced by the argument but not write to them.  Passing
+in the address of an uninitialized object is undefined.
+
+For example, the declaration below indicates that the @code{get_time} function
+only reads the @code{struct tm} object pointed-to by the argument but doesn't
+modify it.  Casting away the constness and modifying the argument is undefined.
+This information may be relied on to emit more efficient code at the call site,
+by one of the @option{-Wunused} options.  Built-in functions such as
+@code{strlen} declare their const pointer arguments with the @code{read_only}
+attribute.
+
+@smallexample
+time_t get_time (const struct tm *) __attribute__ ((read_only));
+@end smallexample
+
+The @var{size-index} argument optionally specifies the number of elements
+of the array referenced by @var{ref-index} the function may read.  Passing
+in an array with fewer elements is diagnosed by @option{-Wstringop-overflow}.
+For example, the declaration below indicates that the function reads as many
+elements of the array referenced by the first argument as specified by
+the second argument.
+
+@smallexample
+int sum (const int *, unsigned) __attribute__ ((read_only (1, 2)));
+@end smallexample
+
+
+@item read_write
+@itemx read_write
+@itemx read_write (@var{arg-index})
+@itemx read_write (@var{ref-index}, @var{size-index})
+@cindex @code{read_write} function attribute
+The @code{read_write} attribute specifies that a function whose by-reference
+arguments denoted by @var{ref-index} (or all such arguments if no
+@var{ref-index} is specified) are used to both read from the objects
+referenced by the argument and to write to them.  Passing in the address
+of an uninitialized object is undefined.
+
+
+@item write_only
+@itemx write_only
+@itemx write_only (@var{arg-index})
+@itemx write_only (@var{ref-index}, @var{size-index})
+@cindex @code{write_only} function attribute
+The @code{write_only} attribute specifies that a function whose by-reference
+arguments of non-@code{const}-qualified types denoted by @var{ref-index} (or
+all such arguments if no @var{ref-index} is specified) are only used to write
+into the objects referenced by the argument but not read from it.  For example,
+the declaration below indicates that the @code{init_tm} function only writes
+to the @code{struct tm} object pointed-to by the argument but doesn't read
+from it.  This information may be relied on to emit more efficient code at
+the call site, and diagnose initialized but otherwise unused objects by one
+of the @option{-Wunused} options.  Built-in functions such as @code{memset}
+and @code{strcpy} declare their non-const pointer argument with
+the @code{write_only} attribute.
+
+@smallexample
+int init_tm (struct tm *) __attribute__ ((write_only));
+@end smallexample
+
+The @var{size-index} argument optionally specifies the number of elements
+of the array referenced by @var{ref-index} the function may write.  Passing
+in an array with fewer elements is diagnosed by @option{-Wstringop-overflow}.
+For example, the declaration below indicates that the function writes as many
+elements of the array referenced by the first argument as specified by
+the second argument.
+
+@smallexample
+void init (int *, unsigned, int) __attribute__ ((write_only (1, 2)));
+@end smallexample
+
+
 @end table
 
 @c This is the end of the target-independent attribute table
diff --git a/gcc/gimple.h b/gcc/gimple.h
index cf1f8da5ae2..4d850a088cf 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/Wsizeof-pointer-memaccess1.c b/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess1.c
index 4281e3b5a8e..a0c1887c1a9 100644
--- a/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess1.c
+++ b/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess1.c
@@ -1,7 +1,7 @@
 /* Test -Wsizeof-pointer-memaccess warnings.  */
 /* { dg-do compile } */
-/* { dg-options "-Wall -Wno-array-bounds -Wno-sizeof-array-argument" } */
-/* { dg-options "-Wall -Wno-array-bounds -Wno-sizeof-array-argument -Wno-c++-compat" { target c } } */
+/* { dg-options "-Wall -Wno-array-bounds -Wno-sizeof-array-argument -Wno-stringop-overflow" } */
+/* { dg-options "-Wall -Wno-array-bounds -Wno-sizeof-array-argument -Wno-c++-compat -Wno-stringop-overflow" { target c } } */
 /* { dg-require-effective-target alloca } */
 
 typedef __SIZE_TYPE__ size_t;
@@ -30,7 +30,7 @@ void foo (void **);
 void
 f1 (void *x)
 {
-  struct A a, *pa1 = &a;
+  struct A a = { }, *pa1 = &a;
   TA *pa2 = &a;
   PA pa3 = &a;
   PTA pa4 = &a;
diff --git a/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess2.c b/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess2.c
index d9a1555e7ce..08ef1695080 100644
--- a/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess2.c
+++ b/gcc/testsuite/c-c++-common/Wsizeof-pointer-memaccess2.c
@@ -484,5 +484,6 @@ f4 (char *x, char **y, int z, char w[64])
   stpncpy (x, s3, sizeof (s3));             /* { dg-warning "call is the same expression as the source; did you mean to use the size of the destination?" } */
 }
 
+/* { dg-prune-output "-Wunused-but-set-variable" } */
 /* { dg-prune-output "\[\n\r\]*writing\[\n\r\]*" } */
 /* { dg-prune-output "\[\n\r\]*reading\[\n\r\]*" } */
diff --git a/gcc/testsuite/gcc.dg/Wstrict-aliasing-bogus-vla-1.c b/gcc/testsuite/gcc.dg/Wstrict-aliasing-bogus-vla-1.c
index 87f5ef9d171..f7f70712baf 100644
--- a/gcc/testsuite/gcc.dg/Wstrict-aliasing-bogus-vla-1.c
+++ b/gcc/testsuite/gcc.dg/Wstrict-aliasing-bogus-vla-1.c
@@ -9,3 +9,5 @@ int main(int argc, char *argv[])
     float y[argc];
     return 0 == __builtin_memcpy(y, x, argc * sizeof(*x));
 }
+
+/* { dg-prune-output "-Wunused-but-set-variable" } */
diff --git a/gcc/testsuite/gcc.dg/Wunused-but-set-var.c b/gcc/testsuite/gcc.dg/Wunused-but-set-var.c
new file mode 100644
index 00000000000..87a0d947432
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wunused-but-set-var.c
@@ -0,0 +1,225 @@
+/* PR c/80806 - gcc does not warn if local array is memset only
+   Verify that attribute write_only implicitly applied to common
+   built-ins or explicitly specified on user-defined functions is
+   used by -Wunused-but-set-variable and -Wunused-but-set-parameter.
+   { dg-do compile }
+   { dg-options "-Wunused-but-set-variable -Wunused-but-set-parameter" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+void bcopy (const void*, void*, size_t);
+void bzero (void*, size_t);
+void* memcpy (void*, const void*, size_t);
+void* memset (void*, int, size_t);
+
+char* strcat (char*, const char*);
+char* strcpy (char*, const char*);
+char* strncat (char*, const char*, size_t);
+char* strncpy (char*, const char*, size_t);
+
+void* malloc (size_t);
+void free (void*);
+
+#define WRONLY(...)  __attribute__ ((write_only (__VA_ARGS__)))
+
+void wronly_1 (void*) WRONLY (1);
+
+struct MemArrays
+{
+  int a[2], b[3][3];
+};
+
+enum { N = sizeof (int) };
+
+void test_bcopy (int a,   /* { dg-warning "-Wunused-but-set-parameter" } */
+		 void *p,
+		 struct MemArrays *pma)
+{
+  int i;                  /* { dg-warning "-Wunused-but-set-variable" } */
+
+  bcopy (p, &a, N);
+  bcopy (p, &i, N);
+
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+
+  bcopy (p, &arr, N);
+  bcopy (p, &arr, sizeof arr);
+  bcopy (p, arr, N);
+  bcopy (p, arr + 0, N);
+  bcopy (p, arr + 1, N);
+  bcopy (p, &arr[0], N);
+  bcopy (p, &arr[1], N);
+  bcopy (p, &arr[2] + 1, N);
+
+  struct MemArrays s;     /* { dg-warning "-Wunused-but-set-variable" } */
+
+  bcopy (p, s.a, N);
+  bcopy (p, s.a, sizeof s.a);
+  bcopy (p, &s.a[0], N);
+  bcopy (p, &s.a[1], N);
+  bcopy (p, &s.b[0], N);
+  bcopy (p, &s.b[0] + 1, N);
+  bcopy (p, &s.b[0][0], N);
+  bcopy (p, &s.b[0][0] + 0, N);
+  bcopy (p, &s.b[0][0] + 1, N);
+  bcopy (p, &s.b[0][1], N);
+  bcopy (p, &s.b[1][1] + 1, N);
+
+  struct MemArrays t;
+
+  bcopy (p, &t.b[0] + 1, 1);
+  bcopy (&t, p, 4);
+
+  bcopy (p, pma->a, N);
+}
+
+
+void test_bzero (int a)   /* { dg-warning "-Wunused-but-set-parameter" } */
+{
+  int i;                  /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+
+  bzero (&a, N);
+  bzero (&a + 0, N);
+  bzero (&i, N);
+  bzero (&i + 0, N);
+
+  bzero (arr, N);
+  bzero (arr + 0, N);
+  bzero (arr + 1, N);
+  bzero (&arr[0] + 1, N);
+  bzero (&arr[0], N);
+  bzero (&arr[1], N);
+}
+
+void test_memset (int a)  /* { dg-warning "-Wunused-but-set-parameter" } */
+{
+  int i;                  /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+
+  memset (&a, 0, N);
+  memset (&i, 0, N);
+  memset (&arr, 0, N);
+}
+
+void test_memcpy (int a,  /* { dg-warning "-Wunused-but-set-parameter" } */
+		  const void *p)
+{
+  int i;                  /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+
+  memcpy (&a, p, N);
+  memcpy (&i, p, N);
+  memcpy (&arr, p, N);
+}
+
+void test_strcat (const char *s)
+{
+  char c = '\0';
+  char arr[8] = "";
+
+  strcat (&c, "");
+  strcat (arr, s);
+}
+
+void test_strcpy (const char *s)
+{
+  char c;                 /* { dg-warning "-Wunused-but-set-variable" } */
+  char d = '\0';          /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr2[8] = "";      /* { dg-warning "-Wunused-but-set-variable" } */
+
+  strcpy (&c, "");
+  strcpy (&d, "");
+  strcpy (arr, s);
+  strcpy (arr2, s);
+}
+
+void test_strncpy (const char *s, unsigned n)
+{
+  char c;                 /* { dg-warning "-Wunused-but-set-variable" } */
+  char d = '\0';          /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr2[8] = "";      /* { dg-warning "-Wunused-but-set-variable" } */
+
+  strncpy (&c, "", 1);
+  strncpy (&d, "", n);
+  strncpy (arr, s, n);
+  strncpy (arr2, s, n);
+}
+
+/* Verify that passing a local pointer initialized to the result of
+   malloc() (anything else should work as well) to free() on doesn't
+   trigger a warning because the pointer value is obviously read.  */
+
+void test_malloc_and_free (size_t n)
+{
+  void *q = malloc (n);
+  free (q);
+}
+
+/* Verify that setting a local variable to a copy of an argument and
+   calling free() on the copy doesn't trigger a warning.  */
+
+void test_free_argcpy (void *p)
+{
+  void *q = p;
+  free (q);
+}
+
+/* Similar to the free test above, verify that setting a local variable
+   to a copy of an argument and calling free() on the copy doesn't trigger
+   a warning.  */
+
+void test_memset_argcpy (void *p, size_t n)
+{
+  void *q = p;
+  memset (q, 0, n);
+}
+
+void test_usrdef (int a)  /* { dg-warning "-Wunused-but-set-parameter" } */
+{
+  int i;                  /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+
+  wronly_1 (&a);
+  wronly_1 (&i);
+  wronly_1 (&arr);
+
+  wronly_1 (arr);
+  wronly_1 (arr + 1);
+  wronly_1 (&arr[1]);
+}
+
+/* Verify that using a pointer to a function works.  */
+typedef struct S
+{
+  void (*pf)(void*) WRONLY (1);
+  struct {
+    struct {
+      void (*pf)(void*) WRONLY (1);
+    } s2;
+  } s1;
+} S;
+
+void test_funcptr (int a,  /* { dg-warning "-Wunused-but-set-parameter" } */
+		   S *ps)
+{
+  int i;                  /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+
+  ps->pf (&a);
+  ps->pf (&i);
+  ps->pf (&arr);
+}
+
+void test_funcptr2 (int a,  /* { dg-warning "-Wunused-but-set-parameter" } */
+		    S *ps)
+{
+  int i;                  /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+
+  ps->s1.s2.pf (&a);
+  ps->s1.s2.pf (&i);
+  ps->s1.s2.pf (&arr);
+}
diff --git a/gcc/testsuite/gcc.dg/attr-alloc_size.c b/gcc/testsuite/gcc.dg/attr-alloc_size.c
index 88a77715805..66fd083f9bc 100644
--- a/gcc/testsuite/gcc.dg/attr-alloc_size.c
+++ b/gcc/testsuite/gcc.dg/attr-alloc_size.c
@@ -32,5 +32,6 @@ test (void)
   p = calloc2 (2, __INT_MAX__ >= 1700000 ? 424242 : __INT_MAX__ / 4, 5);
   strcpy (p, "World");
   strcpy (p, "Hello World"); /* { dg-warning "writing" "strcpy" } */
-}
 
+  (void)&p;   /* suppress -Wunused-but-set-variable */
+}
diff --git a/gcc/testsuite/gcc.dg/attr-read-only-2.c b/gcc/testsuite/gcc.dg/attr-read-only-2.c
new file mode 100644
index 00000000000..53a996fbdd1
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/attr-read-only-2.c
@@ -0,0 +1,85 @@
+/* Test to verify that attributes read_only and read_write are used
+   by -Wuninitialized to issue warnings.
+   { dg-do compile }
+   { dg-options "-Wuninitialized" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+extern char* memcpy (void* restrict, const void* restrict, size_t);
+extern char* memmove (void*, const void*, size_t);
+
+extern char* stpcpy (char* restrict, const char* restrict);
+extern char* stpncpy (char* restrict, const char* restrict, size_t);
+extern char* strcpy (char* restrict, const char* restrict);
+extern char* strncpy (char* restrict, const char* restrict, size_t);
+
+void* malloc (size_t);
+void free (void*);
+
+#define RDONLY(...)  __attribute__ ((read_only (__VA_ARGS__)))
+
+void sink (void*);
+
+/* Due to PR 81809 only the first uninitialized read in a function
+   is diagnosed.  The subsequent ones are not because the variable's
+   address is considered to have escaped.  */
+
+void test_memcpy (char *d)
+{
+  char a7[7];
+  memcpy (d, a7, sizeof a7);  /* { dg-warning ".a7. is used uninitialized" } */
+}
+
+void* test_memmove (void)
+{
+  char a7[7];
+  enum { n = sizeof a7 - 1 };
+  memmove (a7, a7 + 1, n);    /* { dg-warning ".a7. is used uninitialized" } */
+  sink (a7);
+}
+
+void test_strcpy (char *d)
+{
+  char a7[7];
+  strcpy (d, a7);             /* { dg-warning ".a7. is used uninitialized" } */
+}
+
+void test_stpncpy (char *d)
+{
+  char a7[7];
+  stpncpy (d, a7, sizeof a7); /* { dg-warning ".a7. is used uninitialized" } */
+}
+
+void test_strncpy (char *d)
+{
+  char a7[7];
+  strncpy (d, a7, sizeof a7); /* { dg-warning ".a7. is used uninitialized" } */
+}
+
+
+int rdonly_pv_1 (const void*) RDONLY (1);
+
+void test_rdonly_1 (void)
+{
+  int i;
+  rdonly_pv_1 (&i);           /* { dg-warning ".i. is used uninitialized" } */
+}
+
+void test_rdonly_2 (void)
+{
+  int a[2];
+  rdonly_pv_1 (a);            /* { dg-warning ".a. is used uninitialized" } */
+}
+
+
+int rdonly_pv_s_1_2 (const void*, size_t) RDONLY (1, 2);
+
+void test_rdonly_1_2 (void)
+{
+  int a[4];
+  a[2] = 0;
+
+  enum { n = sizeof a[1] };
+  rdonly_pv_s_1_2 (a + 1, n); /* { dg-warning ".a. is used uninitialized" } */
+}
+
diff --git a/gcc/testsuite/gcc.dg/attr-read-only.c b/gcc/testsuite/gcc.dg/attr-read-only.c
new file mode 100644
index 00000000000..a0820b80d42
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/attr-read-only.c
@@ -0,0 +1,63 @@
+/* Test to verify the handling of attribute read_only syntax.
+   { dg-do compile }
+   { dg-options "-Wall -ftrack-macro-expansion=0" } */
+
+#define RDONLY(...)  __attribute__ ((read_only (__VA_ARGS__)))
+
+int  __attribute__ ((read_only))
+rdonly_v_all (void);   /* { dg-error "attribute .read_only. on a function with no pointer arguments" } */
+
+int  __attribute__ ((read_only))
+rdonly_i_all (int);   /* { dg-error "attribute .read_only. on a function with no pointer arguments" } */
+
+int  __attribute__ ((read_only))
+rdonly_pi_all (const int*);
+
+int  __attribute__ ((read_only))
+rdonly_pv_pi_all (const void*, const int*);
+
+int  __attribute__ ((read_only))
+rdonly_i_pc_i_pd_all (int, const char*, int, const double*);
+
+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 .read_only\\(1, 1\\). second operand references non-integer argument type .const void *." } */
+
+int  RDONLY (1, 2)
+rdonly_pcv_pc_1_2 (const void*, char*);   /* { dg-error "attribute .read_only\\(1, 2\\). second operand references non-integer argument type .char *." } */
+
+int  RDONLY (2, 1)
+rdonly_pcd_pcv_2_1 (const double*, const void*);   /* { dg-error "attribute .read_only\\(2, 1\\). second operand references non-integer argument type .const double *." } */
+
+int __attribute__ ((read_only))
+rdonly_pcv_pcv_all (const void*, const void*);
+
+int RDONLY (2, 2)
+rdonly_pi_pcv_2_2 (int*, const void*);   /* { dg-error "second operand references non-integer argument type .const void *." } */
+
+int RDONLY (4)
+rdonly_i_i_i_4 (int, int, int);   /* { dg-error "attribute .read_only\\(4\\). operand exceeds number of arguments 3" } */
+
+int RDONLY (1)
+rdonly_i_1 (int);   /* { dg-error "attribute .read_only\\(1\\). references non-pointer argument type .int." } */
+
+int RDONLY (2)
+rdonly_i_pc (int, char*);   /* { dg-warning "attribute .read_only\\(2\\). operand references non-.const. argument type .char \\\*." } */
+
+int RDONLY (-1)
+rdonly_pcv_m1 (const void*);   /* { dg-error "attribute .read_only\\(-1\\). invalid operand" } */
+
+int RDONLY ("blah")
+rdonly_pcv_str (const void*);   /* { dg-error "attribute .read_only\\(\"blah\"\\). invalid operand" } */
diff --git a/gcc/testsuite/gcc.dg/attr-write-only-2.c b/gcc/testsuite/gcc.dg/attr-write-only-2.c
new file mode 100644
index 00000000000..8ff0ad92eb4
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/attr-write-only-2.c
@@ -0,0 +1,192 @@
+/* Test to verify that attribute write_only is recognized by
+   -Wunused-but-set-variable.
+   { dg-do compile }
+   { dg-options "-Wunused-but-set-variable -Wunused-but-set-parameter" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+void bcopy (const void*, void*, size_t);
+void bzero (void*, size_t);
+void* memcpy (void*, const void*, size_t);
+void* memset (void*, int, size_t);
+
+char* strcat (char*, const char*);
+char* strcpy (char*, const char*);
+char* strncat (char*, const char*, size_t);
+char* strncpy (char*, const char*, size_t);
+
+void* malloc (size_t);
+void free (void*);
+
+#define WRONLY(...)  __attribute__ ((write_only (__VA_ARGS__)))
+
+void wronly_1 (void*) WRONLY (1);
+
+enum { N = sizeof (int) };
+
+void test_bcopy (int a,   /* { dg-warning "-Wunused-but-set-parameter" } */
+		 const void *p)
+{
+  int i;                  /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+
+  bcopy (p, &a, N);
+  bcopy (p, &i, N);
+
+  bcopy (p, &arr, N);
+  bcopy (p, arr, N);
+  bcopy (p, arr + 0, N);
+  bcopy (p, arr + 1, N);
+  bcopy (p, &arr[0], N);
+  bcopy (p, &arr[1], N);
+}
+
+void test_bzero (int a)   /* { dg-warning "-Wunused-but-set-parameter" } */
+{
+  int i;                  /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+
+  bzero (&a, N);
+  bzero (&a + 0, N);
+  bzero (&i, N);
+  bzero (&i + 0, N);
+
+  bzero (arr, N);
+  bzero (arr + 0, N);
+  bzero (arr + 1, N);
+  bzero (&arr[0] + 1, N);
+  bzero (&arr[0], N);
+  bzero (&arr[1], N);
+}
+
+void test_memset (int a)  /* { dg-warning "-Wunused-but-set-parameter" } */
+{
+  int i;                  /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+
+  memset (&a, 0, N);
+  memset (&i, 0, N);
+  memset (&arr, 0, N);
+}
+
+void test_memcpy (int a,  /* { dg-warning "-Wunused-but-set-parameter" } */
+		  const void *p)
+{
+  int i;                  /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+
+  memcpy (&a, p, N);
+  memcpy (&i, p, N);
+  memcpy (&arr, p, N);
+}
+
+void test_strcat (const char *s)
+{
+  char c = '\0';
+  char arr[8] = "";
+
+  strcat (&c, "");
+  strcat (arr, s);
+}
+
+void test_strcpy (const char *s)
+{
+  char c;                 /* { dg-warning "-Wunused-but-set-variable" } */
+  char d = '\0';          /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr2[8] = "";      /* { dg-warning "-Wunused-but-set-variable" } */
+
+  strcpy (&c, "");
+  strcpy (&d, "");
+  strcpy (arr, s);
+  strcpy (arr2, s);
+}
+
+void test_strncpy (const char *s, unsigned n)
+{
+  char c;                 /* { dg-warning "-Wunused-but-set-variable" } */
+  char d = '\0';          /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr2[8] = "";      /* { dg-warning "-Wunused-but-set-variable" } */
+
+  strncpy (&c, "", 1);
+  strncpy (&d, "", n);
+  strncpy (arr, s, n);
+  strncpy (arr2, s, n);
+}
+
+/* Verify that passing a local pointer initialized to the result of
+   malloc() (anything else should work as well) to free() on doesn't
+   trigger a warning because the pointer value is obviously read.  */
+
+void test_malloc_and_free (size_t n)
+{
+  void *q = malloc (n);
+  free (q);
+}
+
+/* Verify that setting a local variable to a copy of an argument and
+   calling free() on the copy doesn't trigger a warning.  */
+
+void test_free_argcpy (void *p)
+{
+  void *q = p;
+  free (q);
+}
+
+/* Similar to the free test above, verify that setting a local variable
+   to a copy of an argument and calling free() on the copy doesn't trigger
+   a warning.  */
+
+void test_memset_argcpy (void *p, size_t n)
+{
+  void *q = p;
+  memset (q, 0, n);
+}
+
+void test_usrdef (int a)  /* { dg-warning "-Wunused-but-set-parameter" } */
+{
+  int i;                  /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+
+  wronly_1 (&a);
+  wronly_1 (&i);
+  wronly_1 (&arr);
+
+  wronly_1 (arr);
+  wronly_1 (arr + 1);
+  wronly_1 (&arr[1]);
+}
+
+/* Verify that using a pointer to a function works.  */
+typedef struct S
+{
+  void (*pf)(void*) WRONLY (1);
+  struct {
+    struct {
+      void (*pf)(void*) WRONLY (1);
+    } s2;
+  } s1;
+} S;
+
+void test_funcptr (int a,  /* { dg-warning "-Wunused-but-set-parameter" } */
+		   S *ps)
+{
+  int i;                  /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+
+  ps->pf (&a);
+  ps->pf (&i);
+  ps->pf (&arr);
+}
+
+void test_funcptr2 (int a,  /* { dg-warning "-Wunused-but-set-parameter" } */
+		    S *ps)
+{
+  int i;                  /* { dg-warning "-Wunused-but-set-variable" } */
+  char arr[8];            /* { dg-warning "-Wunused-but-set-variable" } */
+
+  ps->s1.s2.pf (&a);
+  ps->s1.s2.pf (&i);
+  ps->s1.s2.pf (&arr);
+}
diff --git a/gcc/testsuite/gcc.dg/attr-write-only.c b/gcc/testsuite/gcc.dg/attr-write-only.c
new file mode 100644
index 00000000000..27db9c17ce7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/attr-write-only.c
@@ -0,0 +1,51 @@
+/* Test to verify the handling of attribute write_only syntax.
+   { dg-do compile }
+   { dg-options "-Wall -ftrack-macro-expansion=0" } */
+
+#define WRONLY(...)  __attribute__ ((write_only (__VA_ARGS__)))
+
+void  __attribute__ ((write_only))
+wronly_v_all (void);   /* { dg-error "attribute .write_only. on a function with no pointer arguments" } */
+
+void  __attribute__ ((write_only))
+wronly_i_all (int);   /* { dg-error "attribute .write_only. on a function with no pointer arguments" } */
+
+void wronly_v_1 (void*) WRONLY (1);
+void wronly_iv_2 (int, void*) WRONLY (2);
+void wronly_iiv_3 (int, int, void*) WRONLY (3);
+
+void wronly_v_0p1 (void*) WRONLY (0 + 1);
+void wronly_v_2m1 (void*) WRONLY (2 - 1);
+
+void WRONLY (1, 1)
+wronly_v_1_1 (void*);      /* { dg-error "attribute .write_only\\(1, 1\\). second operand references non-integer argument type .void *." } */
+
+void  WRONLY (1, 2)
+wronly_vv_1_2 (void*, void*);   /* { dg-error "attribute .write_only\\(1, 2\\). second operand references non-integer argument type .void *." } */
+
+void  WRONLY (2, 1)
+wronly_vv_2_1 (void*, void*);   /* { dg-error "attribute .write_only\\(2, 1\\). second operand references non-integer argument type .void *." } */
+
+void  __attribute__ ((write_only))
+wronly_vv_all (void*, void*);
+
+void  WRONLY (1, 1)
+wronly_vv_1_1 (void*, void*);   /* { dg-error "attribute .write_only\\(1, 1\\). second operand references non-integer argument type .void *." } */
+
+void WRONLY (2, 2)
+wronly_vv_1_1 (void*, void*);   /* { dg-error "second operand references non-integer argument type .void *." } */
+
+void WRONLY (4)
+wronly_4_exceed (int, int, int);   /* { dg-error "attribute .write_only\\(4\\). operand exceeds number of arguments 3" } */
+
+void WRONLY (1)
+wronly_1_i (int);   /* { dg-error "attribute .write_only\\(1\\). references non-pointer argument type .int." } */
+
+void WRONLY (2)
+wronly_2_cst (int, const char*);   /* { dg-error "attribute .write_only\\(2\\). operand references .const.-qualified argument type .const char *." } */
+
+void WRONLY (-1)
+wronly_m1_i (void*);    /* { dg-error "attribute .write_only\\(-1\\). invalid operand" } */
+
+void WRONLY ("blah")
+wronly_str_i (void*);   /* { dg-error "attribute .write_only\\(\"blah\"\\). invalid operand" } */
diff --git a/gcc/testsuite/gcc.dg/nonnull-3.c b/gcc/testsuite/gcc.dg/nonnull-3.c
index 6f7bc4f4295..38541128d6e 100644
--- a/gcc/testsuite/gcc.dg/nonnull-3.c
+++ b/gcc/testsuite/gcc.dg/nonnull-3.c
@@ -10,8 +10,8 @@ void
 foo (void *p, char *s)
 {
   __builtin_bzero (NULL, 0);
-  __builtin_bcopy (NULL, p, 0);
-  __builtin_bcopy (p, NULL, 0);
+  __builtin_bcopy (NULL, p, 0);  /* { dg-warning "null" "null pointer check" } */
+  __builtin_bcopy (p, NULL, 0);  /* { dg-warning "null" "null pointer check" } */
   __builtin_bcmp (NULL, p, 0);
   __builtin_bcmp (p, NULL, 0);
   __builtin_index (NULL, 16);  /* { dg-warning "null" "null pointer check" } */
diff --git a/gcc/testsuite/gcc.dg/pr40340-2.c b/gcc/testsuite/gcc.dg/pr40340-2.c
index ea76e10082d..473a27c01b9 100644
--- a/gcc/testsuite/gcc.dg/pr40340-2.c
+++ b/gcc/testsuite/gcc.dg/pr40340-2.c
@@ -1,6 +1,6 @@
-/* PR middle-end/40340 */
+/* PR middle-end/40340 - Fortification warning no longer emitted in inlines */
 /* { dg-do compile } */
-/* { dg-options "-O2 -Wall -Wno-system-headers -fno-tree-dse" } */
+/* { dg-options "-O2 -Wall -Wno-system-headers -Wno-unused -fno-tree-dse" } */
 
 #include "pr40340.h"
 
diff --git a/gcc/testsuite/gcc.dg/pr78768.c b/gcc/testsuite/gcc.dg/pr78768.c
index 72ac3f87129..f931a2a6868 100644
--- a/gcc/testsuite/gcc.dg/pr78768.c
+++ b/gcc/testsuite/gcc.dg/pr78768.c
@@ -11,5 +11,7 @@ int main (void)
 
   __builtin_sprintf (d, "%32s", "x");   /* { dg-warning "directive writing 32 bytes into a region of size 12" "-Wformat-overflow" } */
 
+  (void)&d;   /* Suppress -Wunused-but-set-variable.  */
+
   return 0;
 }
diff --git a/gcc/testsuite/gcc.dg/pr79715.c b/gcc/testsuite/gcc.dg/pr79715.c
index 0f0f90f7122..3f7b38db7c3 100644
--- a/gcc/testsuite/gcc.dg/pr79715.c
+++ b/gcc/testsuite/gcc.dg/pr79715.c
@@ -9,6 +9,8 @@ void f (const char *s)
   char *p = __builtin_malloc (n);
   __builtin_memcpy (p, s, n);
   __builtin_free (p);
+
+  (void)&p;   /* Suppress -Wunused-but-set-variable.  */
 }
 
 void g (const char *s)
@@ -17,6 +19,8 @@ void g (const char *s)
   char *p = __builtin_malloc (n);
   __builtin_strcpy (p, s);
   __builtin_free (p);
+
+  (void)&p;   /* Suppress -Wunused-but-set-variable.  */
 }
 
 /* { dg-final { scan-tree-dump-not "free" "optimized" } }
diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-7.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-7.c
index bf5072e955c..195bf34dde7 100644
--- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-7.c
+++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-snprintf-7.c
@@ -129,6 +129,7 @@ ELIM (5 ==, ga4, "123", "%.3s%.2s", ga4, ga4);
     char a1[N1], a2[N2];				\
     memcpy (a1, init1, sizeof (init1) - 1);		\
     memcpy (a2, init2, sizeof (init2) - 1);		\
+    (void)a1; (void)a2;					\
     const int res = snprintf (0, 0, fmt, __VA_ARGS__);	\
     VERIFY_ELIM (expect res);				\
   } typedef void dummy_typedef
diff --git a/gcc/testsuite/gcc.dg/uninit-builtin.c b/gcc/testsuite/gcc.dg/uninit-builtin.c
new file mode 100644
index 00000000000..469aeaf6a71
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/uninit-builtin.c
@@ -0,0 +1,207 @@
+/* Test to verify that -Wuninitialized detects uninitialized reads
+   by built-in functions.
+   { dg-do compile }
+   { dg-options "-O2 -Wuninitialized" } */
+
+typedef __SIZE_TYPE__ size_t;
+
+void* malloc (size_t);
+void free (void*);
+
+void sink (void*);
+
+/* Due to PR 81809 only the first uninitialized read in a function
+   is diagnosed.  The subsequent ones are not because the variable's
+   address is considered to have escaped.  */
+
+int test_memchr (int c)
+{
+  extern char* memchr (const void*, int, size_t);
+
+  char a3[3];
+  char *p;
+  enum { n = sizeof a3 };
+  p = memchr (a3, c, n);      /* { dg-warning ".a3. is used uninitialized" "memchr" } */
+  return p != 0;
+}
+
+void test_memcpy (char *d)
+{
+  extern char* memcpy (void* restrict, const void* restrict, size_t);
+
+  char a5[5];
+  enum { n = sizeof a5 };
+  memcpy (d, a5, n);          /* { dg-warning ".a5. is used uninitialized" "memcpy" } */
+}
+
+void test_memmove (void)
+{
+  extern char* memmove (void*, const void*, size_t);
+
+  char a7[7];
+  enum { n = sizeof a7 - 1 };
+  memmove (a7, a7 + 1, n);    /* { dg-warning ".a7. is used uninitialized" "memmove" } */
+  sink (a7);
+}
+
+int test_strcat_1 (const char *s)
+{
+  extern char* strcat (char* restrict, const char* restrict);
+
+  char a7[7];
+  strcat (a7, s);             /* { dg-warning ".a7. is used uninitialized" "strcat 1" } */
+  return a7[0];
+}
+
+void test_strcat_2 (char *d)
+{
+  extern char* strcat (char* restrict, const char* restrict);
+
+  char a7[7];
+  strcat (d, a7);             /* { dg-warning ".a7. is used uninitialized" "strcat 2" } */
+}
+
+int test_strchr (const char *s, int c)
+{
+  extern char* strchr (const char*, int);
+
+  char a7[7];
+  s = strchr (a7, c);         /* { dg-warning ".a7. is used uninitialized" "strchr" } */
+  return s != 0;
+}
+
+char* test_strdup (void)
+{
+  extern char* strdup (const char*);
+
+  char a7[7];
+  char *s;
+  s = strdup (a7);            /* { dg-warning ".a7. is used uninitialized" "strdup" } */
+  return s;
+}
+
+int test_strstr_1 (const char *s)
+{
+  extern char* strstr (const char*, const char*);
+
+  char a7[7];
+  s = strstr (a7, s);         /* { dg-warning ".a7. is used uninitialized" "strstr 1" } */
+  return s != 0;
+}
+
+int test_strstr_2 (const char *s)
+{
+  extern char* strstr (const char*, const char*);
+
+  char a7[7];
+  s = strstr (s, a7);         /* { dg-warning ".a7. is used uninitialized" "strstr 2" } */
+  return s != 0;
+}
+
+void test_strcpy (char *d)
+{
+  extern char* strcpy (char* restrict, const char* restrict);
+
+  char a7[7];
+  strcpy (d, a7);             /* { dg-warning ".a7. is used uninitialized" "strcpy" } */
+}
+
+void test_stpcpy (char *d)
+{
+  extern char* stpcpy (char* restrict, const char* restrict);
+
+  char a7[7];
+  stpcpy (d, a7);             /* { dg-warning ".a7. is used uninitialized" "stpcpy" } */
+}
+
+void test_stpncpy (char *d)
+{
+  extern char* stpncpy (char* restrict, const char* restrict, size_t);
+
+  char a7[7];
+  stpncpy (d, a7, sizeof a7); /* { dg-warning ".a7. is used uninitialized" "stpncpy" } */
+}
+
+int test_strcmp_1 (const char *s)
+{
+  extern int strcmp (const char*, const char*);
+
+  char a7[7];
+  int i;
+  i = strcmp (a7, s);         /* { dg-warning ".a7. is used uninitialized" "strcmp" } */
+  return i;
+}
+
+int test_strcmp_2 (const char *s)
+{
+  extern int strcmp (const char*, const char*);
+
+  char a7[7];
+  int i;
+  i = strcmp (s, a7);         /* { dg-warning ".a7. is used uninitialized" "strcmp" } */
+  return i;
+}
+
+int test_strncat_1 (const char *s, size_t n)
+{
+  extern char* strncat (char* restrict, const char* restrict, size_t);
+
+  char a7[7];
+  s = strncat (a7, s, n);     /* { dg-warning ".a7. is used uninitialized" "strncat 1" } */
+  return s != 0;
+}
+
+int test_strncat_2 (char *d, size_t n)
+{
+  extern char* strncat (char* restrict, const char* restrict, size_t);
+
+  char a7[7];
+  d = strncat (d, a7, n);     /* { dg-warning ".a7. is used uninitialized" "strncat 2" } */
+  return d != 0;
+}
+
+int test_strncmp_1 (const char *s, size_t n)
+{
+  extern int strncmp (const char*, const char*, size_t);
+
+  char a7[7];
+  int i;
+  i = strncmp (a7, s, n);     /* { dg-warning ".a7. is used uninitialized" "strncmp 1" } */
+  return i;
+}
+
+int test_strncmp_2 (const char *s, size_t n)
+{
+  extern int strncmp (const char*, const char*, size_t);
+
+  char a7[7];
+  int i;
+  i = strncmp (s, a7, n);     /* { dg-warning ".a7. is used uninitialized" "strncmp 2" } */
+  return i;
+}
+
+void test_strncpy (char *d)
+{
+  extern char* strncpy (char* restrict, const char* restrict, size_t);
+
+  char a7[7];
+  strncpy (d, a7, sizeof a7); /* { dg-warning ".a7. is used uninitialized" "strncpy" } */
+}
+
+char* test_strndup (size_t n)
+{
+  extern char* strndup (const char*, size_t);
+
+  char a7[7];
+  char *s;
+  s = strndup (a7, n);        /* { dg-warning ".a7. is used uninitialized" "strndup" } */
+  return s;
+}
+
+size_t test_strlen (void)
+{
+  extern size_t strlen (const char*);
+
+  char a7[7];
+  return strlen (a7);         /* { dg-warning ".a7. is used uninitialized" "strlen" } */
+}
diff --git a/gcc/tree-ssa-uninit.c b/gcc/tree-ssa-uninit.c
index fe8f8f0bc28..b9d455159cb 100644
--- a/gcc/tree-ssa-uninit.c
+++ b/gcc/tree-ssa-uninit.c
@@ -34,6 +34,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "params.h"
 #include "tree-cfg.h"
 #include "cfghooks.h"
+#include "attribs.h"
 
 /* This implements the pass that does predicate aware warning on uses of
    possibly uninitialized variables.  The pass first collects the set of
@@ -218,8 +219,172 @@ check_defs (ao_ref *ref, tree vdef, void *data_)
   return true;
 }
 
+/* Helper of warn_uninitialized_vars.  Determine if Gimple statement
+   STMT reads or may read any uninitialized operand and if so, diagnose
+   the access.  */
+
+static void
+maybe_warn_uninit_access (gimple *stmt, tree arg, ao_ref &ref,
+			  bool always_executed,
+			  unsigned *vdef_cnt, unsigned *oracle_cnt,
+			  unsigned *limit, bool maybe_uninit)
+{
+  /* Do not warn if the base was marked so or this is a
+     hard register var.  */
+  tree base = ao_ref_base (&ref);
+  if ((VAR_P (base)
+       && DECL_HARD_REGISTER (base))
+      || TREE_NO_WARNING (base))
+    return;
+
+  /* Do not warn if the access is fully outside of the
+     variable.  */
+  if (DECL_P (base)
+      && known_ne (ref.size, -1)
+      && known_eq (ref.max_size, ref.size)
+      && (known_le (ref.offset + ref.size, 0)
+	  || (known_ge (ref.offset, 0)
+	      && DECL_SIZE (base)
+	      && TREE_CODE (DECL_SIZE (base)) == INTEGER_CST
+	      && tree_int_cst_le (DECL_SIZE (base),
+				  build_int_cstu (sizetype, ref.offset)))))
+    return;
+
+  /* Limit the walking to a constant number of stmts after
+     we overcommit quadratic behavior for small functions
+     and O(n) behavior.  */
+  if (*oracle_cnt > 128 * 128
+      && *oracle_cnt > *vdef_cnt * 2)
+    *limit = 32;
+  check_defs_data data;
+  bool fentry_reached = false;
+  data.found_may_defs = false;
+
+  int res = walk_aliased_vdefs (&ref, gimple_vuse (stmt),
+				check_defs, &data, NULL,
+				&fentry_reached, *limit);
+  if (res == -1)
+    {
+      *oracle_cnt += *limit;
+      return;
+    }
+  oracle_cnt += res;
+  if (data.found_may_defs)
+    return;
+  /* Do not warn if it can be initialized outside this function.
+     If we did not reach function entry then we found killing
+     clobbers on all paths to entry.  */
+  if (fentry_reached
+      /* ???  We'd like to use ref_may_alias_global_p but that
+	 excludes global readonly memory and thus we get bougs
+	 warnings from p = cond ? "a" : "b" for example.  */
+      && (!VAR_P (base)
+	  || is_global_var (base)))
+    return;
+
+  /* We didn't find any may-defs so on all paths either
+     reached function entry or a killing clobber.  */
+  location_t location
+    = linemap_resolve_location (line_table, gimple_location (stmt),
+				LRK_SPELLING_LOCATION, NULL);
+  if (always_executed)
+    {
+      if (warning_at (location, OPT_Wuninitialized,
+		      "%qE is used uninitialized in this function",
+		      arg))
+	/* ???  This is only effective for decls as in
+	   gcc.dg/uninit-B-O0.c.  Avoid doing this for
+	   maybe-uninit uses as it may hide important
+	   locations.  */
+	TREE_NO_WARNING (arg) = 1;
+    }
+  else if (maybe_uninit)
+    warning_at (location, OPT_Wmaybe_uninitialized,
+		"%qE may be used uninitialized in this function",
+		arg);
+}
+
+/* Helper of warn_uninitialized_vars.  Determine if Gimple statement
+   STMT reads any uninitialized operand.  */
+
+static void
+maybe_warn_uninit_access (gimple *stmt, bool always_executed,
+			  unsigned *vdef_cnt, unsigned *oracle_cnt,
+			  unsigned *limit, bool maybe_uninit)
+{
+  if (gimple_assign_load_p (stmt)
+      && gimple_has_location (stmt))
+    {
+      bool has_bit_insert = false;
+
+      tree lhs = gimple_assign_lhs (stmt);
+
+      /* Do not warn if the access is then used for a BIT_INSERT_EXPR. */
+      if (TREE_CODE (lhs) == SSA_NAME)
+	{
+	  use_operand_p luse_p;
+	  imm_use_iterator liter;
+
+	  FOR_EACH_IMM_USE_FAST (luse_p, liter, lhs)
+	    {
+	      gimple *use_stmt = USE_STMT (luse_p);
+	      /* BIT_INSERT_EXPR first operand should not be considered
+		 a use for the purpose of uninit warnings.  */
+	      if (gassign *ass = dyn_cast <gassign *> (use_stmt))
+		{
+		  if (gimple_assign_rhs_code (ass) == BIT_INSERT_EXPR
+		      && luse_p->use == gimple_assign_rhs1_ptr (ass))
+		    {
+		      has_bit_insert = true;
+		      break;
+		    }
+		}
+	    }
+	}
+
+      if (!has_bit_insert)
+	{
+	  tree arg = gimple_assign_rhs1 (stmt);
+	  if (TREE_NO_WARNING (arg))
+	    return;
+
+	  ao_ref ref;
+	  ao_ref_init (&ref, arg);
+
+	  maybe_warn_uninit_access (stmt, arg, ref, always_executed, vdef_cnt,
+				    oracle_cnt, limit, maybe_uninit);
+	  return;
+	}
+    }
+
+  if (!is_gimple_call (stmt))
+    return;
+
+  /* Iterate over arguments to functions declared with attribute
+     read_only or read_write and diagnose those that are not
+     initialized.  */
+  const attr_access::Kind access_kinds[] = {
+    attr_access::read_only, attr_access::read_write
+  };
+
+  for (unsigned i = 0; i != sizeof access_kinds / sizeof *access_kinds; ++i)
+    {
+      attr_access acc = { };
+      acc.kind = access_kinds[i];
+      while (get_attr_access_ptr_and_size (stmt, &acc))
+	{
+	  ao_ref ref;
+	  ao_ref_init_from_ptr_and_size (&ref, acc.ptr, acc.size);
+	  tree arg = ref.base;
+
+	  maybe_warn_uninit_access (stmt, arg, ref, always_executed, vdef_cnt,
+				    oracle_cnt, limit, maybe_uninit);
+	}
+    }
+}
+
 static unsigned int
-warn_uninitialized_vars (bool warn_possibly_uninitialized)
+warn_uninitialized_vars (bool maybe_uninit)
 {
   gimple_stmt_iterator gsi;
   basic_block bb;
@@ -236,7 +401,6 @@ warn_uninitialized_vars (bool warn_possibly_uninitialized)
 	  gimple *stmt = gsi_stmt (gsi);
 	  use_operand_p use_p;
 	  ssa_op_iter op_iter;
-	  tree use;
 
 	  if (is_gimple_debug (stmt))
 	    continue;
@@ -253,13 +417,13 @@ warn_uninitialized_vars (bool warn_possibly_uninitialized)
 		      && use_p->use == gimple_assign_rhs1_ptr (ass))
 		    continue;
 		}
-	      use = USE_FROM_PTR (use_p);
+	      tree use = USE_FROM_PTR (use_p);
 	      if (always_executed)
 		warn_uninit (OPT_Wuninitialized, use, SSA_NAME_VAR (use),
 			     SSA_NAME_VAR (use),
 			     "%qD is used uninitialized in this function", stmt,
 			     UNKNOWN_LOCATION);
-	      else if (warn_possibly_uninitialized)
+	      else if (maybe_uninit)
 		warn_uninit (OPT_Wmaybe_uninitialized, use, SSA_NAME_VAR (use),
 			     SSA_NAME_VAR (use),
 			     "%qD may be used uninitialized in this function",
@@ -271,115 +435,9 @@ warn_uninitialized_vars (bool warn_possibly_uninitialized)
 	  if (gimple_vdef (stmt))
 	    vdef_cnt++;
 
-	  if (gimple_assign_load_p (stmt)
-	      && gimple_has_location (stmt))
-	    {
-	      tree rhs = gimple_assign_rhs1 (stmt);
-	      tree lhs = gimple_assign_lhs (stmt);
-	      bool has_bit_insert = false;
-	      use_operand_p luse_p;
-	      imm_use_iterator liter;
-
-	      if (TREE_NO_WARNING (rhs))
-		continue;
-
-	      ao_ref ref;
-	      ao_ref_init (&ref, rhs);
-
-	      /* Do not warn if the base was marked so or this is a
-	         hard register var.  */
-	      tree base = ao_ref_base (&ref);
-	      if ((VAR_P (base)
-		   && DECL_HARD_REGISTER (base))
-		  || TREE_NO_WARNING (base))
-		continue;
-
-	      /* Do not warn if the access is fully outside of the
-	         variable.  */
-	      poly_int64 decl_size;
-	      if (DECL_P (base)
-		  && known_size_p (ref.size)
-		  && ((known_eq (ref.max_size, ref.size)
-		       && known_le (ref.offset + ref.size, 0))
-		      || (known_ge (ref.offset, 0)
-			  && DECL_SIZE (base)
-			  && poly_int_tree_p (DECL_SIZE (base), &decl_size)
-			  && known_le (decl_size, ref.offset))))
-		continue;
-
-	      /* Do not warn if the access is then used for a BIT_INSERT_EXPR. */
-	      if (TREE_CODE (lhs) == SSA_NAME)
-	        FOR_EACH_IMM_USE_FAST (luse_p, liter, lhs)
-		  {
-		    gimple *use_stmt = USE_STMT (luse_p);
-                    /* BIT_INSERT_EXPR first operand should not be considered
-		       a use for the purpose of uninit warnings.  */
-		    if (gassign *ass = dyn_cast <gassign *> (use_stmt))
-		      {
-			if (gimple_assign_rhs_code (ass) == BIT_INSERT_EXPR
-			    && luse_p->use == gimple_assign_rhs1_ptr (ass))
-			  {
-			    has_bit_insert = true;
-			    break;
-			  }
-		      }
-		  }
-	      if (has_bit_insert)
-		continue;
-
-	      /* Limit the walking to a constant number of stmts after
-	         we overcommit quadratic behavior for small functions
-		 and O(n) behavior.  */
-	      if (oracle_cnt > 128 * 128
-		  && oracle_cnt > vdef_cnt * 2)
-		limit = 32;
-	      check_defs_data data;
-	      bool fentry_reached = false;
-	      data.found_may_defs = false;
-	      use = gimple_vuse (stmt);
-	      int res = walk_aliased_vdefs (&ref, use,
-					    check_defs, &data, NULL,
-					    &fentry_reached, limit);
-	      if (res == -1)
-		{
-		  oracle_cnt += limit;
-		  continue;
-		}
-	      oracle_cnt += res;
-	      if (data.found_may_defs)
-		continue;
-	      /* Do not warn if it can be initialized outside this function.
-	         If we did not reach function entry then we found killing
-		 clobbers on all paths to entry.  */
-	      if (fentry_reached
-		  /* ???  We'd like to use ref_may_alias_global_p but that
-		     excludes global readonly memory and thus we get bougs
-		     warnings from p = cond ? "a" : "b" for example.  */
-		  && (!VAR_P (base)
-		      || is_global_var (base)))
-		continue;
-
-	      /* We didn't find any may-defs so on all paths either
-	         reached function entry or a killing clobber.  */
-	      location_t location
-		= linemap_resolve_location (line_table, gimple_location (stmt),
-					    LRK_SPELLING_LOCATION, NULL);
-	      if (always_executed)
-		{
-		  if (warning_at (location, OPT_Wuninitialized,
-				  "%qE is used uninitialized in this function",
-				  rhs))
-		    /* ???  This is only effective for decls as in
-		       gcc.dg/uninit-B-O0.c.  Avoid doing this for
-		       maybe-uninit uses as it may hide important
-		       locations.  */
-		    TREE_NO_WARNING (rhs) = 1;
-		}
-	      else if (warn_possibly_uninitialized)
-		warning_at (location, OPT_Wmaybe_uninitialized,
-			    "%qE may be used uninitialized in this function",
-			    rhs);
-	    }
+	  maybe_warn_uninit_access (stmt, always_executed,
+				    &vdef_cnt, &oracle_cnt, &limit,
+				    maybe_uninit);
 	}
     }
 
@@ -2666,7 +2724,7 @@ pass_late_warn_uninitialized::execute (function *fun)
   /* Re-do the plain uninitialized variable check, as optimization may have
      straightened control flow.  Do this first so that we don't accidentally
      get a "may be" warning when we'd have seen an "is" warning later.  */
-  warn_uninitialized_vars (/*warn_possibly_uninitialized=*/1);
+  warn_uninitialized_vars (/*maybe_uninit=*/1);
 
   timevar_push (TV_TREE_UNINIT);
 
@@ -2736,7 +2794,7 @@ execute_early_warn_uninitialized (void)
      optimization we need to warn here about "may be uninitialized".  */
   calculate_dominance_info (CDI_POST_DOMINATORS);
 
-  warn_uninitialized_vars (/*warn_possibly_uninitialized=*/!optimize);
+  warn_uninitialized_vars (/*maybe_uninit=*/!optimize);
 
   /* Post-dominator information cannot be reliably updated.  Free it
      after the use.  */

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

* Re: [WIP PATCH] add object access attributes (PR 83859)
  2019-09-29 19:52 [WIP PATCH] add object access attributes (PR 83859) Martin Sebor
@ 2019-09-30  7:37 ` Richard Biener
  2019-09-30 15:41   ` Martin Sebor
  2019-09-30 21:34 ` Joseph Myers
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 19+ messages in thread
From: Richard Biener @ 2019-09-30  7:37 UTC (permalink / raw)
  To: Martin Sebor; +Cc: gcc-patches

On Sun, Sep 29, 2019 at 9:52 PM Martin Sebor <msebor@gmail.com> wrote:
>
> -Wstringop-overflow detects a subset of past-the-end read and write
> accesses by built-in functions such as memcpy and strcpy.  It relies
> on the functions' effects the knowledge of which is hardwired into
> GCC.  Although it's possible for users to create wrappers for their
> own functions to detect similar problems, it's quite cumbersome and
> so only lightly used outside system libraries like Glibc.  Even Glibc
> only checks for buffer overflow and not for reading past the end.
>
> PR 83859 asks to expose the same checking that GCC does natively for
> built-in calls via a function attribute that associates a pointer
> argument with the size argument, such as:
>
>    __attribute__((buffer_size (1, 2))) void
>    f (char* dst, size_t dstsize);
>
> The attached patch is my initial stab at providing this feature by
> introducing three new attributes:
>
>    * read_only (ptr-argno, size-argno)
>    * read_only (ptr-argno, size-argno)
>    * read_write (ptr-argno, size-argno)
>
> As requested, the attributes associate a pointer parameter to
> a function with a size parameter.  In addition, they also specify
> how the function accesses the object the pointer points to: either
> it only reads from it, or it only writes to it, or it does both.
>
> Besides enabling the same buffer overflow detection as for built-in
> string functions they also let GCC issue -Wuninitialized warnings
> for uninitialized objects passed to read-only functions by reference,
> and -Wunused-but-set warnings for objects passed to write-only
> functions that are otherwise unused (PR 80806).  The -Wununitialized
> part is done. The -Wunused-but-set detection is implemented only in
> the C FE and not yet in C++.
>
> Besides the diagnostic improvements above the attributes also open
> up optimization opportunities such as DCE.  I'm still working on this
> and so it's not yet part of the initial patch.

There's the "fn spec" attribute which you can use for the optimization
part.  Note "fn spec" also likes to know whether the address of the
argument escapes and whether the argument is only dereferenced
directly or also indirectly (when passing a pointer to a struct is
transitively reachable memory through the pointer accessed or not?).

So you should at least make sure to document the full
semantics of your proposed read_only/write_only/read_write atributes.

I guess that "read_only" means that direct accesses do not write
but the attribute does not constrain indirect accesses?

Note "fn spec" doesn't offer read/write constraints when not at the
same time constraining escaping since when a pointer escapes
through a call we cannot make any optimization for a positional
read/write constraint since a function can access global memory
(and all escaped pointed-to data ends up as "global memory").
There's no way to tell (via "fn spec") that the function only accesses
memory reachable via function arguments.

So I'm not sure there's a 1:1 mapping for your desired semantics
to "fn spec" plus your desired semantics may offer no
opportunity for optimization.  Useful would be if read_only
would map to "R" and read_write and write_only would map to "W".

Richard.

> I plan to finish the patch for GCC 10 but I don't expect to have
> the time to start taking advantage of the attributes for optimization
> until GCC 11.
>
> Besides regression testing on x86_64-linux, I also tested the patch
> by compiling Binutils/GDB, Glibc, and the Linux kernel with it.  It
> found no new problems but caused a handful of -Wunused-but-set-variable
> false positives due to an outstanding bug in the C front-end introduced
> by the patch that I still need to fix.
>
> Martin

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

* Re: [WIP PATCH] add object access attributes (PR 83859)
  2019-09-30  7:37 ` Richard Biener
@ 2019-09-30 15:41   ` Martin Sebor
  0 siblings, 0 replies; 19+ messages in thread
From: Martin Sebor @ 2019-09-30 15:41 UTC (permalink / raw)
  To: Richard Biener; +Cc: gcc-patches

On 9/30/19 1:37 AM, Richard Biener wrote:
> On Sun, Sep 29, 2019 at 9:52 PM Martin Sebor <msebor@gmail.com> wrote:
>>
>> -Wstringop-overflow detects a subset of past-the-end read and write
>> accesses by built-in functions such as memcpy and strcpy.  It relies
>> on the functions' effects the knowledge of which is hardwired into
>> GCC.  Although it's possible for users to create wrappers for their
>> own functions to detect similar problems, it's quite cumbersome and
>> so only lightly used outside system libraries like Glibc.  Even Glibc
>> only checks for buffer overflow and not for reading past the end.
>>
>> PR 83859 asks to expose the same checking that GCC does natively for
>> built-in calls via a function attribute that associates a pointer
>> argument with the size argument, such as:
>>
>>     __attribute__((buffer_size (1, 2))) void
>>     f (char* dst, size_t dstsize);
>>
>> The attached patch is my initial stab at providing this feature by
>> introducing three new attributes:
>>
>>     * read_only (ptr-argno, size-argno)
>>     * read_only (ptr-argno, size-argno)
>>     * read_write (ptr-argno, size-argno)
>>
>> As requested, the attributes associate a pointer parameter to
>> a function with a size parameter.  In addition, they also specify
>> how the function accesses the object the pointer points to: either
>> it only reads from it, or it only writes to it, or it does both.
>>
>> Besides enabling the same buffer overflow detection as for built-in
>> string functions they also let GCC issue -Wuninitialized warnings
>> for uninitialized objects passed to read-only functions by reference,
>> and -Wunused-but-set warnings for objects passed to write-only
>> functions that are otherwise unused (PR 80806).  The -Wununitialized
>> part is done. The -Wunused-but-set detection is implemented only in
>> the C FE and not yet in C++.
>>
>> Besides the diagnostic improvements above the attributes also open
>> up optimization opportunities such as DCE.  I'm still working on this
>> and so it's not yet part of the initial patch.
> 
> There's the "fn spec" attribute which you can use for the optimization
> part.  Note "fn spec" also likes to know whether the address of the
> argument escapes and whether the argument is only dereferenced
> directly or also indirectly (when passing a pointer to a struct is
> transitively reachable memory through the pointer accessed or not?).

Thanks, I'll look into those.

> 
> So you should at least make sure to document the full
> semantics of your proposed read_only/write_only/read_write atributes.
> 
> I guess that "read_only" means that direct accesses do not write
> but the attribute does not constrain indirect accesses?

Correct.  Some other annotation is necessary to constrain those.
A read-only restrict-qualified pointer would do that.  Because
the read-only attribute only applies to const pointers the only
purpose it serves in that combination is the association with
the size parameter.  As in:

   __attribute__ ((read_only (2, 3))) void*
   memcpy (void* restrict, const void *restrict, size_t);

without the pointer-size association the above reduces to this:

   __attribute__ ((read_only)) void*
   memcpy (void* restrict, const void *restrict, size_t);

which should ultimately have the same effect as the plain

   void*
   memcpy (void* restrict, const void *restrict, size_t);

As a future extension (GCC 11 or beyond) I'd like to look into
allowing the read/write attributes on object and mainly subobject
declarations.  There too, read-only should be paired with restrict
to express the same constraint.

> Note "fn spec" doesn't offer read/write constraints when not at the
> same time constraining escaping since when a pointer escapes
> through a call we cannot make any optimization for a positional
> read/write constraint since a function can access global memory
> (and all escaped pointed-to data ends up as "global memory").
> There's no way to tell (via "fn spec") that the function only accesses
> memory reachable via function arguments.

In my WIP patch I have a no_side_effect attribute that further
constrains what a function can do.  It's just like pure except
that it lets the function access objects passed to it by reference.

> 
> So I'm not sure there's a 1:1 mapping for your desired semantics
> to "fn spec" plus your desired semantics may offer no
> opportunity for optimization.  Useful would be if read_only
> would map to "R" and read_write and write_only would map to "W".

I didn't know about the R and W fnspecs.  Let me look into them
for GCC 11 to see if I can make use of them for the optimization.

Martin

> 
> Richard.
> 
>> I plan to finish the patch for GCC 10 but I don't expect to have
>> the time to start taking advantage of the attributes for optimization
>> until GCC 11.
>>
>> Besides regression testing on x86_64-linux, I also tested the patch
>> by compiling Binutils/GDB, Glibc, and the Linux kernel with it.  It
>> found no new problems but caused a handful of -Wunused-but-set-variable
>> false positives due to an outstanding bug in the C front-end introduced
>> by the patch that I still need to fix.
>>
>> Martin

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

* Re: [WIP PATCH] add object access attributes (PR 83859)
  2019-09-29 19:52 [WIP PATCH] add object access attributes (PR 83859) Martin Sebor
  2019-09-30  7:37 ` Richard Biener
@ 2019-09-30 21:34 ` Joseph Myers
  2019-10-01  2:36   ` Martin Sebor
  2019-10-17 16:44 ` [PING] " Martin Sebor
  2019-10-27 17:37 ` Jeff Law
  3 siblings, 1 reply; 19+ messages in thread
From: Joseph Myers @ 2019-09-30 21:34 UTC (permalink / raw)
  To: Martin Sebor; +Cc: gcc-patches

On Sun, 29 Sep 2019, Martin Sebor wrote:

> PR 83859 asks to expose the same checking that GCC does natively for
> built-in calls via a function attribute that associates a pointer
> argument with the size argument, such as:

I'll also note that, as mentioned in that bug (but more specifically 
covered by the separate bug 50584) it would make sense to have similar 
warnings with [static] parameter array declarators.  And that the C2x 
charter includes a principle that new interfaces should have the array 
size before the array parameter to allow parameters to be declared using 
VLA syntax like that, so it's plausible the [static] case will be useful 
for more functions in future (although it's up to library implementations 
exactly what form they use in their headers, given they have other 
considerations such as C++ compatibility).

Hopefully the infrastructure in this patch will facilitate future support 
for such diagnostics in the [static] case.

-- 
Joseph S. Myers
joseph@codesourcery.com

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

* Re: [WIP PATCH] add object access attributes (PR 83859)
  2019-09-30 21:34 ` Joseph Myers
@ 2019-10-01  2:36   ` Martin Sebor
  0 siblings, 0 replies; 19+ messages in thread
From: Martin Sebor @ 2019-10-01  2:36 UTC (permalink / raw)
  To: Joseph Myers; +Cc: gcc-patches

On 9/30/19 3:34 PM, Joseph Myers wrote:
> On Sun, 29 Sep 2019, Martin Sebor wrote:
> 
>> PR 83859 asks to expose the same checking that GCC does natively for
>> built-in calls via a function attribute that associates a pointer
>> argument with the size argument, such as:
> 
> I'll also note that, as mentioned in that bug (but more specifically
> covered by the separate bug 50584) it would make sense to have similar
> warnings with [static] parameter array declarators.  And that the C2x
> charter includes a principle that new interfaces should have the array
> size before the array parameter to allow parameters to be declared using
> VLA syntax like that, so it's plausible the [static] case will be useful
> for more functions in future (although it's up to library implementations
> exactly what form they use in their headers, given they have other
> considerations such as C++ compatibility).
> 
> Hopefully the infrastructure in this patch will facilitate future support
> for such diagnostics in the [static] case.

Thanks for the reminder!  I will make sure the infrastructure makes
the VLA checking possible, even if it's not actually implemented for
GCC 10.

Martin

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

* [PING] [WIP PATCH] add object access attributes (PR 83859)
  2019-09-29 19:52 [WIP PATCH] add object access attributes (PR 83859) Martin Sebor
  2019-09-30  7:37 ` Richard Biener
  2019-09-30 21:34 ` Joseph Myers
@ 2019-10-17 16:44 ` Martin Sebor
  2019-10-24 14:42   ` [PING 2] " Martin Sebor
  2019-10-27 17:37 ` Jeff Law
  3 siblings, 1 reply; 19+ messages in thread
From: Martin Sebor @ 2019-10-17 16:44 UTC (permalink / raw)
  To: gcc-patches, Jeff Law

Ping: https://gcc.gnu.org/ml/gcc-patches/2019-09/msg01690.html

Other than the suggestions I got for optimization (for GCC 11)
and additional buffer overflow detection for [static] arrays),
is there any feedback on the patch itself?  Jeff?

Martin

On 9/29/19 1:51 PM, Martin Sebor wrote:
> -Wstringop-overflow detects a subset of past-the-end read and write
> accesses by built-in functions such as memcpy and strcpy.  It relies
> on the functions' effects the knowledge of which is hardwired into
> GCC.  Although it's possible for users to create wrappers for their
> own functions to detect similar problems, it's quite cumbersome and
> so only lightly used outside system libraries like Glibc.  Even Glibc
> only checks for buffer overflow and not for reading past the end.
> 
> PR 83859 asks to expose the same checking that GCC does natively for
> built-in calls via a function attribute that associates a pointer
> argument with the size argument, such as:
> 
>    __attribute__((buffer_size (1, 2))) void
>    f (char* dst, size_t dstsize);
> 
> The attached patch is my initial stab at providing this feature by
> introducing three new attributes:
> 
>    * read_only (ptr-argno, size-argno)
>    * read_only (ptr-argno, size-argno)
>    * read_write (ptr-argno, size-argno)
> 
> As requested, the attributes associate a pointer parameter to
> a function with a size parameter.  In addition, they also specify
> how the function accesses the object the pointer points to: either
> it only reads from it, or it only writes to it, or it does both.
> 
> Besides enabling the same buffer overflow detection as for built-in
> string functions they also let GCC issue -Wuninitialized warnings
> for uninitialized objects passed to read-only functions by reference,
> and -Wunused-but-set warnings for objects passed to write-only
> functions that are otherwise unused (PR 80806).  The -Wununitialized
> part is done. The -Wunused-but-set detection is implemented only in
> the C FE and not yet in C++.
> 
> Besides the diagnostic improvements above the attributes also open
> up optimization opportunities such as DCE.  I'm still working on this
> and so it's not yet part of the initial patch.
> 
> I plan to finish the patch for GCC 10 but I don't expect to have
> the time to start taking advantage of the attributes for optimization
> until GCC 11.
> 
> Besides regression testing on x86_64-linux, I also tested the patch
> by compiling Binutils/GDB, Glibc, and the Linux kernel with it.  It
> found no new problems but caused a handful of -Wunused-but-set-variable 
> false positives due to an outstanding bug in the C front-end introduced
> by the patch that I still need to fix.
> 
> Martin

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

* [PING 2] [WIP PATCH] add object access attributes (PR 83859)
  2019-10-17 16:44 ` [PING] " Martin Sebor
@ 2019-10-24 14:42   ` Martin Sebor
  0 siblings, 0 replies; 19+ messages in thread
From: Martin Sebor @ 2019-10-24 14:42 UTC (permalink / raw)
  To: gcc-patches, Jeff Law

Ping: https://gcc.gnu.org/ml/gcc-patches/2019-09/msg01690.html

On 10/17/2019 10:28 AM, Martin Sebor wrote:
> Ping: https://gcc.gnu.org/ml/gcc-patches/2019-09/msg01690.html
> 
> Other than the suggestions I got for optimization (for GCC 11)
> and additional buffer overflow detection for [static] arrays),
> is there any feedback on the patch itself?  Jeff?
> 
> Martin
> 
> On 9/29/19 1:51 PM, Martin Sebor wrote:
>> -Wstringop-overflow detects a subset of past-the-end read and write
>> accesses by built-in functions such as memcpy and strcpy.  It relies
>> on the functions' effects the knowledge of which is hardwired into
>> GCC.  Although it's possible for users to create wrappers for their
>> own functions to detect similar problems, it's quite cumbersome and
>> so only lightly used outside system libraries like Glibc.  Even Glibc
>> only checks for buffer overflow and not for reading past the end.
>>
>> PR 83859 asks to expose the same checking that GCC does natively for
>> built-in calls via a function attribute that associates a pointer
>> argument with the size argument, such as:
>>
>>    __attribute__((buffer_size (1, 2))) void
>>    f (char* dst, size_t dstsize);
>>
>> The attached patch is my initial stab at providing this feature by
>> introducing three new attributes:
>>
>>    * read_only (ptr-argno, size-argno)
>>    * read_only (ptr-argno, size-argno)
>>    * read_write (ptr-argno, size-argno)
>>
>> As requested, the attributes associate a pointer parameter to
>> a function with a size parameter.  In addition, they also specify
>> how the function accesses the object the pointer points to: either
>> it only reads from it, or it only writes to it, or it does both.
>>
>> Besides enabling the same buffer overflow detection as for built-in
>> string functions they also let GCC issue -Wuninitialized warnings
>> for uninitialized objects passed to read-only functions by reference,
>> and -Wunused-but-set warnings for objects passed to write-only
>> functions that are otherwise unused (PR 80806).  The -Wununitialized
>> part is done. The -Wunused-but-set detection is implemented only in
>> the C FE and not yet in C++.
>>
>> Besides the diagnostic improvements above the attributes also open
>> up optimization opportunities such as DCE.  I'm still working on this
>> and so it's not yet part of the initial patch.
>>
>> I plan to finish the patch for GCC 10 but I don't expect to have
>> the time to start taking advantage of the attributes for optimization
>> until GCC 11.
>>
>> Besides regression testing on x86_64-linux, I also tested the patch
>> by compiling Binutils/GDB, Glibc, and the Linux kernel with it.  It
>> found no new problems but caused a handful of 
>> -Wunused-but-set-variable false positives due to an outstanding bug in 
>> the C front-end introduced
>> by the patch that I still need to fix.
>>
>> Martin
> 

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

* Re: [WIP PATCH] add object access attributes (PR 83859)
  2019-09-29 19:52 [WIP PATCH] add object access attributes (PR 83859) Martin Sebor
                   ` (2 preceding siblings ...)
  2019-10-17 16:44 ` [PING] " Martin Sebor
@ 2019-10-27 17:37 ` Jeff Law
  2019-10-28 10:18   ` Richard Biener
  2019-11-15 21:41   ` Martin Sebor
  3 siblings, 2 replies; 19+ messages in thread
From: Jeff Law @ 2019-10-27 17:37 UTC (permalink / raw)
  To: Martin Sebor, gcc-patches

On 9/29/19 1:51 PM, Martin Sebor wrote:
> -Wstringop-overflow detects a subset of past-the-end read and write
> accesses by built-in functions such as memcpy and strcpy.  It relies
> on the functions' effects the knowledge of which is hardwired into
> GCC.  Although it's possible for users to create wrappers for their
> own functions to detect similar problems, it's quite cumbersome and
> so only lightly used outside system libraries like Glibc.  Even Glibc
> only checks for buffer overflow and not for reading past the end.
> 
> PR 83859 asks to expose the same checking that GCC does natively for
> built-in calls via a function attribute that associates a pointer
> argument with the size argument, such as:
> 
>   __attribute__((buffer_size (1, 2))) void
>   f (char* dst, size_t dstsize);
> 
> The attached patch is my initial stab at providing this feature by
> introducing three new attributes:
> 
>   * read_only (ptr-argno, size-argno)
>   * read_only (ptr-argno, size-argno)
>   * read_write (ptr-argno, size-argno)
> 
> As requested, the attributes associate a pointer parameter to
> a function with a size parameter.  In addition, they also specify
> how the function accesses the object the pointer points to: either
> it only reads from it, or it only writes to it, or it does both.
> 
> Besides enabling the same buffer overflow detection as for built-in
> string functions they also let GCC issue -Wuninitialized warnings
> for uninitialized objects passed to read-only functions by reference,
> and -Wunused-but-set warnings for objects passed to write-only
> functions that are otherwise unused (PR 80806).  The -Wununitialized
> part is done. The -Wunused-but-set detection is implemented only in
> the C FE and not yet in C++.
> 
> Besides the diagnostic improvements above the attributes also open
> up optimization opportunities such as DCE.  I'm still working on this
> and so it's not yet part of the initial patch.
> 
> I plan to finish the patch for GCC 10 but I don't expect to have
> the time to start taking advantage of the attributes for optimization
> until GCC 11.
> 
> Besides regression testing on x86_64-linux, I also tested the patch
> by compiling Binutils/GDB, Glibc, and the Linux kernel with it.  It
> found no new problems but caused a handful of -Wunused-but-set-variable
> false positives due to an outstanding bug in the C front-end introduced
> by the patch that I still need to fix.
> 
> Martin
> 
> gcc-80806.diff
> 
> PR c/80806 - gcc does not warn if local array is memset only
> PR middle-end/83859 - attribute to associate buffer and its size
> 
> gcc/ChangeLog:
> 
> 	PR c/80806
> 	PR middle-end/83859
> 	* builtin-attrs.def (ATTR_NO_SIDE_EFFECT): New.
> 	(ATTR_READ_ONLY, ATTR_READ_WRITE, ATTR_WRITE_ONLY): New.
> 	(ATTR_NOTHROW_WRONLY1_LEAF, ATTR_NOTHROW_WRONLY1_2_LEAF): New.
> 	(ATTR_NOTHROW_WRONLY1_3_LEAF, ATTR_NOTHROW_WRONLY2_3_LEAF): New.
> 	(ATTR_RET1_NOTHROW_WRONLY1_LEAF, ATTR_RET1_NOTHROW_WRONLY1_3_LEAF): New.
> 	(ATTR_RET1_NOTHROW_NONNULL_RDONLY2_LEAF): New.
> 	(ATTR_RET1_NOTHROW_NONNULL_RDWR1_RDONLY2_LEAF): New.
> 	(ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_3_LEAF): New.
> 	(ATTR_RET1_NOTHROW_WRONLY1_RDONLY2_LEAF): New.
> 	(ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_LEAF): New.
> 	(ATTR_MALLOC_NOTHROW_NONNULL_RDONLY1_LEAF): New.
> 	(ATTR_PURE_NOTHROW_NONNULL_RDONLY1_LEAF): New.
> 	(ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF): New.
> 	(ATTR_RETNONNULL_RDONLY2_3_NOTHROW_LEAF): New.
> 	(ATTR_RETNONNULL_WRONLY1_3_RDONLY2_3_NOTHROW_LEAF): New.
> 	(ATTR_PURE_NOTHROW_NONNULL_RDONLY1_3_LEAF): New.
> 	(ATTR_PURE_NOTHROW_NONNULL_RDONLY1_2_3_LEAF): New.
> 	(ATTR_RETNONNULL_RDONLY2_NOTHROW_LEAF): New.
> 	(ATTR_RETNONNULL_WRONLY1_RDONLY2_NOTHROW_LEAF): New.
> 	(ATTR_RETNONNULL_WRONLY1_3_RDONLY2_NOTHROW_LEAF): New.
> 	* builtins.c (check_access): Make extern.  Consistently set
> 	the no-warning bit after issuing a warning.
> 	* builtins.h (check_access): Declare.
> 	* builtins.def (bcopy, bzero, index, memchr, memcmp, memcpy): Add
> 	read_only and write_only attributes.
> 	(memset, rindex, stpcpy, stpncpy, strcasecmp, strcat): Same.
> 	(strchr, strcmp, strcpy, strcspn, strdup, strndup, strlen): Same.
> 	(strncasecmp, strncat, strncmp, strncpy, strrchr, strspn, strstr): Same.
> 	(free, __memcpy_chk, __memmove_chk, __memset_chk): Same.
> 	(__strcpy_chk, __strncpy_chk): Same.
> 	* 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 (no_side_effect): Document new attribute.
> 	(read_only, write_only, read_write): Same.
> 	* tree-ssa-uninit.c (maybe_warn_uninit_accesss): New functions.
> 	(warn_uninitialized_vars): Rename argument.  Factor out code into
> 	maybe_warn_uninit_accesss.  Call it.
> 
> gcc/c/ChangeLog:
> 
> 	PR c/80806
> 	PR middle-end/83859
> 	* c-parser.c (c_parser::no_set_read, no_init, in_arg): New members.
> 	(c_parser_expr_list): Add argument.
> 	(c_parser_attribute): Pass a new argument to get_nonnull_operand.
> 	(c_parser_initializer): Set parser->in_init.
> 	(c_parser_binary_expression): Use parser->no_set_read.
> 	(c_parser_unary_expression): Same.
> 	(c_parser_sizeof_expression): Use parser->in_arg.
> 	(c_parser_postfix_expression_after_primary): Adjust.
> 	(is_write_only_p): New function.
> 	(c_parser_expr_list): Add argument.
> 	Avoid setting DECL_READ_P for decls passed to write-only function
> 	arguments.
> 	(c_parser_objc_keywordexpr): Pass a new argument to c_parser_expr_list.
> 	(c_parser_oacc_wait_list): Same.
> 	* c-tree.h (parser_build_binary_op): Add argument.
> 	* c-typeck.c (default_conversion): Same.  Use it.
> 	(parser_build_binary_op): Same.
> 	(build_binary_op): Same.
> 
> gcc/c-family/ChangeLog:
> 
> 	PR c/80806
> 	PR middle-end/83859
> 	* c-attribs.c (handle_no_side_effect_attribute): New function.
> 	(handle_read_only_attribute, handle_write_only_attribute): Same.
> 	(handle_read_write_attribute): Same.
> 	(c_common_attribute_table): Add new attributes.
> 	(get_argument_type): New function.
> 	(handle_rdwr_attributes): Same.
> 	(has_attribute): Add argument to callback signature.  Pass to it
> 	a new value.
> 	(get_nonnull_operand): Rename...
> 	(get_attribute_operand): ...to this.
> 	* c-common.c (get_nonnull_operand): Rename...
> 	(get_attribute_operand): ...to this.
> 	(build_binary_op): Add a new argument.
> 	(default_conversion): Same.
> 	(has_attribute): Adjust argument type.
> 
> gcc/cp/ChangeLog:
> 
> 	PR c/80806
> 	PR middle-end/83859
> 	* typeck.c (cp_default_conversion): Add argument.
> 	(build_binary_op): Same.
> 
> gcc/testsuite/ChangeLog:
> 
> 	PR c/80806
> 	PR middle-end/83859
> 	* c-c++-common/Wsizeof-pointer-memaccess1.c: Adjust.
> 	* c-c++-common/Wsizeof-pointer-memaccess2.c: Adjust.
> 	* gcc.dg/Wstrict-aliasing-bogus-vla-1.c: Adjust.
> 	* gcc/testsuite/gcc.dg/Wunused-but-set-var.c: New test.
> 	* gcc.dg/attr-alloc_size.c: Adjust.
> 	* gcc/testsuite/gcc.dg/attr-read-only-2.c: New test.
> 	* gcc/testsuite/gcc.dg/attr-read-only.c: New test.
> 	* gcc/testsuite/gcc.dg/attr-write-only-2.c: New test.
> 	* gcc/testsuite/gcc.dg/attr-write-only.c: New test.
> 	* gcc.dg/nonnull-3.c: Adjust.
> 	* gcc.dg/pr40340-2.c: Adjust.
> 	* gcc.dg/pr78768.c: Adjust.
> 	* gcc.dg/pr79715.c: Adjust.
> 	* gcc.dg/tree-ssa/builtin-snprintf-7.c: Adjust.
> 	* gcc/testsuite/gcc.dg/uninit-builtin.c: New test.
You mention that this is almost done, but not yet finished.  If I were
working on this I could have split this up into adding the support for
the attributes first without trying to use them anywhere first.  Then a
separate patch that adds the attribute to the builtins, then a patch
that exploited the new attributes to do something useful.

Please try to avoid creating large patches that are so easily broken up.
 It just makes review harder than it needs to be and slows down getting
the patches integrated.

Your ChangeLog references "no_side_effect" in a few places.  But I don't
see any of those changes actually in the patch, except for adding it in
builtin-attrs.def.  I'm guessing those are supposed to all be part of a
follow-up patch?

FWIW, I believe there may be a DSE case that your patches allow us to
handle.  Essentially we had a case where DSE didn't fire because we
didn't have detailed information about how the operands to one of the
mem* or str* calls was used.  I don't expect you to own this missing opt
as part of this submission.


> diff --git a/gcc/attribs.h b/gcc/attribs.h
> index 23a7321e04a..0e2320701b1 100644
> --- a/gcc/attribs.h
> +++ b/gcc/attribs.h
> @@ -218,4 +218,36 @@ lookup_attribute_by_prefix (const char *attr_name, tree list)
>      }
>  }
>  
> +/* Description of a function argument declared attribute read_only,
> +   read_write, or write_only.  Used as an "iterator" over all such
> +   arguments in a function declaration or call.  */
> +
> +struct attr_access
> +{
> +  /* Attribute chain for the given function declaration.  */
> +  tree attrs;
> +
> +  /* The attribute pointer argument.  */
> +  tree ptr;
> +  /* The size of the pointed-to object or NULL when not specified.  */
> +  tree size;
> +
> +  /* The tree node corresponding to the current argument in the chain
> +     of formal function arguments in a call to the given function.
> +     Used by attributes that specify that relevant arguments are of
> +     the given kind, as in
> +       strcmp (const char*, const char*) __attribute__ ((read_only))  */
> +  tree argchain;
> +
> +  /* The zero-based number of each of the formal function arguments.  */
> +  unsigned ptrarg;
> +  unsigned sizarg;
> +  enum Kind { read_only, write_only, read_write };
Nit: Mixed case "Kind" use either KIND or kind.





> diff --git a/gcc/c/c-typeck.c b/gcc/c/c-typeck.c
> index d4e12eb93d1..2e439c43b91 100644
> --- a/gcc/c/c-typeck.c
> +++ b/gcc/c/c-typeck.c
> @@ -2146,14 +2146,30 @@ perform_integral_promotions (tree exp)
>     In addition, manifest constants symbols are replaced by their values.  */
>  
>  tree
> -default_conversion (tree exp)
> +default_conversion (tree exp, bool read_p /* = true */)
Please update the function comment to describe the new argument.  I
think you need to do that for c_parser_expr_list and
parser_build_binary_op and build_binary_op.


> diff --git a/gcc/gimple.h b/gcc/gimple.h
> index cf1f8da5ae2..4d850a088cf 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"
How important is this (ie, how painful is it to have the .c/.cc files
include function.h/basic-block.h?  We generally frown on having one
header include others like this.



> diff --git a/gcc/tree-ssa-uninit.c b/gcc/tree-ssa-uninit.c
> index fe8f8f0bc28..b9d455159cb 100644
> --- a/gcc/tree-ssa-uninit.c
> +++ b/gcc/tree-ssa-uninit.c
Something else to consider (as a separate follow-up).

IIUC your code adds checking arguments at call sites which is a nice
improvement.  There may be BZs related to this issue.

Another concept we might want to consider based on what I've seen pop up
fairly often in code is the concept of "must write".  In the caller we'd
consider passing a must-write object to a function call as
initialization which would cut down on false positives.  We could verify
the behavior in the callee.

Also, I think Fortran has the concept of "Intent" which does largely the
same thing you're doing.  You might consider reaching out to the Fortran
front-end folks and see if they can encode their Intent information into
your attributes.  I believe there's BZs related to using Intent
information to avoid false positives from the uninit warning pass.

I don't see anything terribly concerning.  Looking forward to the final
iteration here.

jeff

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

* Re: [WIP PATCH] add object access attributes (PR 83859)
  2019-10-27 17:37 ` Jeff Law
@ 2019-10-28 10:18   ` Richard Biener
  2019-11-15 21:41   ` Martin Sebor
  1 sibling, 0 replies; 19+ messages in thread
From: Richard Biener @ 2019-10-28 10:18 UTC (permalink / raw)
  To: Jeff Law; +Cc: Martin Sebor, gcc-patches

On Sun, Oct 27, 2019 at 6:32 PM Jeff Law <law@redhat.com> wrote:
>
> On 9/29/19 1:51 PM, Martin Sebor wrote:
> > -Wstringop-overflow detects a subset of past-the-end read and write
> > accesses by built-in functions such as memcpy and strcpy.  It relies
> > on the functions' effects the knowledge of which is hardwired into
> > GCC.  Although it's possible for users to create wrappers for their
> > own functions to detect similar problems, it's quite cumbersome and
> > so only lightly used outside system libraries like Glibc.  Even Glibc
> > only checks for buffer overflow and not for reading past the end.
> >
> > PR 83859 asks to expose the same checking that GCC does natively for
> > built-in calls via a function attribute that associates a pointer
> > argument with the size argument, such as:
> >
> >   __attribute__((buffer_size (1, 2))) void
> >   f (char* dst, size_t dstsize);
> >
> > The attached patch is my initial stab at providing this feature by
> > introducing three new attributes:
> >
> >   * read_only (ptr-argno, size-argno)
> >   * read_only (ptr-argno, size-argno)
> >   * read_write (ptr-argno, size-argno)
> >
> > As requested, the attributes associate a pointer parameter to
> > a function with a size parameter.  In addition, they also specify
> > how the function accesses the object the pointer points to: either
> > it only reads from it, or it only writes to it, or it does both.
> >
> > Besides enabling the same buffer overflow detection as for built-in
> > string functions they also let GCC issue -Wuninitialized warnings
> > for uninitialized objects passed to read-only functions by reference,
> > and -Wunused-but-set warnings for objects passed to write-only
> > functions that are otherwise unused (PR 80806).  The -Wununitialized
> > part is done. The -Wunused-but-set detection is implemented only in
> > the C FE and not yet in C++.
> >
> > Besides the diagnostic improvements above the attributes also open
> > up optimization opportunities such as DCE.  I'm still working on this
> > and so it's not yet part of the initial patch.
> >
> > I plan to finish the patch for GCC 10 but I don't expect to have
> > the time to start taking advantage of the attributes for optimization
> > until GCC 11.
> >
> > Besides regression testing on x86_64-linux, I also tested the patch
> > by compiling Binutils/GDB, Glibc, and the Linux kernel with it.  It
> > found no new problems but caused a handful of -Wunused-but-set-variable
> > false positives due to an outstanding bug in the C front-end introduced
> > by the patch that I still need to fix.
> >
> > Martin
> >
> > gcc-80806.diff
> >
> > PR c/80806 - gcc does not warn if local array is memset only
> > PR middle-end/83859 - attribute to associate buffer and its size
> >
> > gcc/ChangeLog:
> >
> >       PR c/80806
> >       PR middle-end/83859
> >       * builtin-attrs.def (ATTR_NO_SIDE_EFFECT): New.
> >       (ATTR_READ_ONLY, ATTR_READ_WRITE, ATTR_WRITE_ONLY): New.
> >       (ATTR_NOTHROW_WRONLY1_LEAF, ATTR_NOTHROW_WRONLY1_2_LEAF): New.
> >       (ATTR_NOTHROW_WRONLY1_3_LEAF, ATTR_NOTHROW_WRONLY2_3_LEAF): New.
> >       (ATTR_RET1_NOTHROW_WRONLY1_LEAF, ATTR_RET1_NOTHROW_WRONLY1_3_LEAF): New.
> >       (ATTR_RET1_NOTHROW_NONNULL_RDONLY2_LEAF): New.
> >       (ATTR_RET1_NOTHROW_NONNULL_RDWR1_RDONLY2_LEAF): New.
> >       (ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_3_LEAF): New.
> >       (ATTR_RET1_NOTHROW_WRONLY1_RDONLY2_LEAF): New.
> >       (ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_LEAF): New.
> >       (ATTR_MALLOC_NOTHROW_NONNULL_RDONLY1_LEAF): New.
> >       (ATTR_PURE_NOTHROW_NONNULL_RDONLY1_LEAF): New.
> >       (ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF): New.
> >       (ATTR_RETNONNULL_RDONLY2_3_NOTHROW_LEAF): New.
> >       (ATTR_RETNONNULL_WRONLY1_3_RDONLY2_3_NOTHROW_LEAF): New.
> >       (ATTR_PURE_NOTHROW_NONNULL_RDONLY1_3_LEAF): New.
> >       (ATTR_PURE_NOTHROW_NONNULL_RDONLY1_2_3_LEAF): New.
> >       (ATTR_RETNONNULL_RDONLY2_NOTHROW_LEAF): New.
> >       (ATTR_RETNONNULL_WRONLY1_RDONLY2_NOTHROW_LEAF): New.
> >       (ATTR_RETNONNULL_WRONLY1_3_RDONLY2_NOTHROW_LEAF): New.
> >       * builtins.c (check_access): Make extern.  Consistently set
> >       the no-warning bit after issuing a warning.
> >       * builtins.h (check_access): Declare.
> >       * builtins.def (bcopy, bzero, index, memchr, memcmp, memcpy): Add
> >       read_only and write_only attributes.
> >       (memset, rindex, stpcpy, stpncpy, strcasecmp, strcat): Same.
> >       (strchr, strcmp, strcpy, strcspn, strdup, strndup, strlen): Same.
> >       (strncasecmp, strncat, strncmp, strncpy, strrchr, strspn, strstr): Same.
> >       (free, __memcpy_chk, __memmove_chk, __memset_chk): Same.
> >       (__strcpy_chk, __strncpy_chk): Same.
> >       * 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 (no_side_effect): Document new attribute.
> >       (read_only, write_only, read_write): Same.
> >       * tree-ssa-uninit.c (maybe_warn_uninit_accesss): New functions.
> >       (warn_uninitialized_vars): Rename argument.  Factor out code into
> >       maybe_warn_uninit_accesss.  Call it.
> >
> > gcc/c/ChangeLog:
> >
> >       PR c/80806
> >       PR middle-end/83859
> >       * c-parser.c (c_parser::no_set_read, no_init, in_arg): New members.
> >       (c_parser_expr_list): Add argument.
> >       (c_parser_attribute): Pass a new argument to get_nonnull_operand.
> >       (c_parser_initializer): Set parser->in_init.
> >       (c_parser_binary_expression): Use parser->no_set_read.
> >       (c_parser_unary_expression): Same.
> >       (c_parser_sizeof_expression): Use parser->in_arg.
> >       (c_parser_postfix_expression_after_primary): Adjust.
> >       (is_write_only_p): New function.
> >       (c_parser_expr_list): Add argument.
> >       Avoid setting DECL_READ_P for decls passed to write-only function
> >       arguments.
> >       (c_parser_objc_keywordexpr): Pass a new argument to c_parser_expr_list.
> >       (c_parser_oacc_wait_list): Same.
> >       * c-tree.h (parser_build_binary_op): Add argument.
> >       * c-typeck.c (default_conversion): Same.  Use it.
> >       (parser_build_binary_op): Same.
> >       (build_binary_op): Same.
> >
> > gcc/c-family/ChangeLog:
> >
> >       PR c/80806
> >       PR middle-end/83859
> >       * c-attribs.c (handle_no_side_effect_attribute): New function.
> >       (handle_read_only_attribute, handle_write_only_attribute): Same.
> >       (handle_read_write_attribute): Same.
> >       (c_common_attribute_table): Add new attributes.
> >       (get_argument_type): New function.
> >       (handle_rdwr_attributes): Same.
> >       (has_attribute): Add argument to callback signature.  Pass to it
> >       a new value.
> >       (get_nonnull_operand): Rename...
> >       (get_attribute_operand): ...to this.
> >       * c-common.c (get_nonnull_operand): Rename...
> >       (get_attribute_operand): ...to this.
> >       (build_binary_op): Add a new argument.
> >       (default_conversion): Same.
> >       (has_attribute): Adjust argument type.
> >
> > gcc/cp/ChangeLog:
> >
> >       PR c/80806
> >       PR middle-end/83859
> >       * typeck.c (cp_default_conversion): Add argument.
> >       (build_binary_op): Same.
> >
> > gcc/testsuite/ChangeLog:
> >
> >       PR c/80806
> >       PR middle-end/83859
> >       * c-c++-common/Wsizeof-pointer-memaccess1.c: Adjust.
> >       * c-c++-common/Wsizeof-pointer-memaccess2.c: Adjust.
> >       * gcc.dg/Wstrict-aliasing-bogus-vla-1.c: Adjust.
> >       * gcc/testsuite/gcc.dg/Wunused-but-set-var.c: New test.
> >       * gcc.dg/attr-alloc_size.c: Adjust.
> >       * gcc/testsuite/gcc.dg/attr-read-only-2.c: New test.
> >       * gcc/testsuite/gcc.dg/attr-read-only.c: New test.
> >       * gcc/testsuite/gcc.dg/attr-write-only-2.c: New test.
> >       * gcc/testsuite/gcc.dg/attr-write-only.c: New test.
> >       * gcc.dg/nonnull-3.c: Adjust.
> >       * gcc.dg/pr40340-2.c: Adjust.
> >       * gcc.dg/pr78768.c: Adjust.
> >       * gcc.dg/pr79715.c: Adjust.
> >       * gcc.dg/tree-ssa/builtin-snprintf-7.c: Adjust.
> >       * gcc/testsuite/gcc.dg/uninit-builtin.c: New test.
> You mention that this is almost done, but not yet finished.  If I were
> working on this I could have split this up into adding the support for
> the attributes first without trying to use them anywhere first.  Then a
> separate patch that adds the attribute to the builtins, then a patch
> that exploited the new attributes to do something useful.
>
> Please try to avoid creating large patches that are so easily broken up.
>  It just makes review harder than it needs to be and slows down getting
> the patches integrated.
>
> Your ChangeLog references "no_side_effect" in a few places.  But I don't
> see any of those changes actually in the patch, except for adding it in
> builtin-attrs.def.  I'm guessing those are supposed to all be part of a
> follow-up patch?
>
> FWIW, I believe there may be a DSE case that your patches allow us to
> handle.  Essentially we had a case where DSE didn't fire because we
> didn't have detailed information about how the operands to one of the
> mem* or str* calls was used.  I don't expect you to own this missing opt
> as part of this submission.
>
>
> > diff --git a/gcc/attribs.h b/gcc/attribs.h
> > index 23a7321e04a..0e2320701b1 100644
> > --- a/gcc/attribs.h
> > +++ b/gcc/attribs.h
> > @@ -218,4 +218,36 @@ lookup_attribute_by_prefix (const char *attr_name, tree list)
> >      }
> >  }
> >
> > +/* Description of a function argument declared attribute read_only,
> > +   read_write, or write_only.  Used as an "iterator" over all such
> > +   arguments in a function declaration or call.  */
> > +
> > +struct attr_access
> > +{
> > +  /* Attribute chain for the given function declaration.  */
> > +  tree attrs;
> > +
> > +  /* The attribute pointer argument.  */
> > +  tree ptr;
> > +  /* The size of the pointed-to object or NULL when not specified.  */
> > +  tree size;
> > +
> > +  /* The tree node corresponding to the current argument in the chain
> > +     of formal function arguments in a call to the given function.
> > +     Used by attributes that specify that relevant arguments are of
> > +     the given kind, as in
> > +       strcmp (const char*, const char*) __attribute__ ((read_only))  */
> > +  tree argchain;
> > +
> > +  /* The zero-based number of each of the formal function arguments.  */
> > +  unsigned ptrarg;
> > +  unsigned sizarg;
> > +  enum Kind { read_only, write_only, read_write };
> Nit: Mixed case "Kind" use either KIND or kind.
>
>
>
>
>
> > diff --git a/gcc/c/c-typeck.c b/gcc/c/c-typeck.c
> > index d4e12eb93d1..2e439c43b91 100644
> > --- a/gcc/c/c-typeck.c
> > +++ b/gcc/c/c-typeck.c
> > @@ -2146,14 +2146,30 @@ perform_integral_promotions (tree exp)
> >     In addition, manifest constants symbols are replaced by their values.  */
> >
> >  tree
> > -default_conversion (tree exp)
> > +default_conversion (tree exp, bool read_p /* = true */)
> Please update the function comment to describe the new argument.  I
> think you need to do that for c_parser_expr_list and
> parser_build_binary_op and build_binary_op.
>
>
> > diff --git a/gcc/gimple.h b/gcc/gimple.h
> > index cf1f8da5ae2..4d850a088cf 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"
> How important is this (ie, how painful is it to have the .c/.cc files
> include function.h/basic-block.h?  We generally frown on having one
> header include others like this.
>
>
>
> > diff --git a/gcc/tree-ssa-uninit.c b/gcc/tree-ssa-uninit.c
> > index fe8f8f0bc28..b9d455159cb 100644
> > --- a/gcc/tree-ssa-uninit.c
> > +++ b/gcc/tree-ssa-uninit.c
> Something else to consider (as a separate follow-up).
>
> IIUC your code adds checking arguments at call sites which is a nice
> improvement.  There may be BZs related to this issue.
>
> Another concept we might want to consider based on what I've seen pop up
> fairly often in code is the concept of "must write".  In the caller we'd
> consider passing a must-write object to a function call as
> initialization which would cut down on false positives.  We could verify
> the behavior in the callee.
>
> Also, I think Fortran has the concept of "Intent" which does largely the
> same thing you're doing.  You might consider reaching out to the Fortran
> front-end folks and see if they can encode their Intent information into
> your attributes.  I believe there's BZs related to using Intent
> information to avoid false positives from the uninit warning pass.
>
> I don't see anything terribly concerning.  Looking forward to the final
> iteration here.

Note I've chickened out to exposing these kind of annotations to users
with 'fn spec' which provides fortran Intent(IN/OUT) semantics to optimizers.
I think we need to formally define what those attributes guarantee for
optimizations first and arrive at consensus here.

Once we have those attributes we have to support whatever broken things
we did.

So I'd rather have them in the implementation namespace for now only.

Richard.

> jeff
>

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

* Re: [WIP PATCH] add object access attributes (PR 83859)
  2019-10-27 17:37 ` Jeff Law
  2019-10-28 10:18   ` Richard Biener
@ 2019-11-15 21:41   ` Martin Sebor
  2019-11-18  9:00     ` Richard Biener
  1 sibling, 1 reply; 19+ messages in thread
From: Martin Sebor @ 2019-11-15 21:41 UTC (permalink / raw)
  To: Jeff Law, gcc-patches

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

On 10/27/19 11:31 AM, Jeff Law wrote:
> On 9/29/19 1:51 PM, Martin Sebor wrote:
>> -Wstringop-overflow detects a subset of past-the-end read and write
>> accesses by built-in functions such as memcpy and strcpy.  It relies
>> on the functions' effects the knowledge of which is hardwired into
>> GCC.  Although it's possible for users to create wrappers for their
>> own functions to detect similar problems, it's quite cumbersome and
>> so only lightly used outside system libraries like Glibc.  Even Glibc
>> only checks for buffer overflow and not for reading past the end.
>>
>> PR 83859 asks to expose the same checking that GCC does natively for
>> built-in calls via a function attribute that associates a pointer
>> argument with the size argument, such as:
>>
>>    __attribute__((buffer_size (1, 2))) void
>>    f (char* dst, size_t dstsize);
>>
>> The attached patch is my initial stab at providing this feature by
>> introducing three new attributes:
>>
>>    * read_only (ptr-argno, size-argno)
>>    * read_only (ptr-argno, size-argno)
>>    * read_write (ptr-argno, size-argno)
>>
>> As requested, the attributes associate a pointer parameter to
>> a function with a size parameter.  In addition, they also specify
>> how the function accesses the object the pointer points to: either
>> it only reads from it, or it only writes to it, or it does both.
>>
>> Besides enabling the same buffer overflow detection as for built-in
>> string functions they also let GCC issue -Wuninitialized warnings
>> for uninitialized objects passed to read-only functions by reference,
>> and -Wunused-but-set warnings for objects passed to write-only
>> functions that are otherwise unused (PR 80806).  The -Wununitialized
>> part is done. The -Wunused-but-set detection is implemented only in
>> the C FE and not yet in C++.
>>
>> Besides the diagnostic improvements above the attributes also open
>> up optimization opportunities such as DCE.  I'm still working on this
>> and so it's not yet part of the initial patch.
>>
>> I plan to finish the patch for GCC 10 but I don't expect to have
>> the time to start taking advantage of the attributes for optimization
>> until GCC 11.
>>
>> Besides regression testing on x86_64-linux, I also tested the patch
>> by compiling Binutils/GDB, Glibc, and the Linux kernel with it.  It
>> found no new problems but caused a handful of -Wunused-but-set-variable
>> false positives due to an outstanding bug in the C front-end introduced
>> by the patch that I still need to fix.
>>
>> Martin
>>
>> gcc-80806.diff
>>
>> PR c/80806 - gcc does not warn if local array is memset only
>> PR middle-end/83859 - attribute to associate buffer and its size
>>
>> gcc/ChangeLog:
>>
>> 	PR c/80806
>> 	PR middle-end/83859
>> 	* builtin-attrs.def (ATTR_NO_SIDE_EFFECT): New.
>> 	(ATTR_READ_ONLY, ATTR_READ_WRITE, ATTR_WRITE_ONLY): New.
>> 	(ATTR_NOTHROW_WRONLY1_LEAF, ATTR_NOTHROW_WRONLY1_2_LEAF): New.
>> 	(ATTR_NOTHROW_WRONLY1_3_LEAF, ATTR_NOTHROW_WRONLY2_3_LEAF): New.
>> 	(ATTR_RET1_NOTHROW_WRONLY1_LEAF, ATTR_RET1_NOTHROW_WRONLY1_3_LEAF): New.
>> 	(ATTR_RET1_NOTHROW_NONNULL_RDONLY2_LEAF): New.
>> 	(ATTR_RET1_NOTHROW_NONNULL_RDWR1_RDONLY2_LEAF): New.
>> 	(ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_3_LEAF): New.
>> 	(ATTR_RET1_NOTHROW_WRONLY1_RDONLY2_LEAF): New.
>> 	(ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_LEAF): New.
>> 	(ATTR_MALLOC_NOTHROW_NONNULL_RDONLY1_LEAF): New.
>> 	(ATTR_PURE_NOTHROW_NONNULL_RDONLY1_LEAF): New.
>> 	(ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF): New.
>> 	(ATTR_RETNONNULL_RDONLY2_3_NOTHROW_LEAF): New.
>> 	(ATTR_RETNONNULL_WRONLY1_3_RDONLY2_3_NOTHROW_LEAF): New.
>> 	(ATTR_PURE_NOTHROW_NONNULL_RDONLY1_3_LEAF): New.
>> 	(ATTR_PURE_NOTHROW_NONNULL_RDONLY1_2_3_LEAF): New.
>> 	(ATTR_RETNONNULL_RDONLY2_NOTHROW_LEAF): New.
>> 	(ATTR_RETNONNULL_WRONLY1_RDONLY2_NOTHROW_LEAF): New.
>> 	(ATTR_RETNONNULL_WRONLY1_3_RDONLY2_NOTHROW_LEAF): New.
>> 	* builtins.c (check_access): Make extern.  Consistently set
>> 	the no-warning bit after issuing a warning.
>> 	* builtins.h (check_access): Declare.
>> 	* builtins.def (bcopy, bzero, index, memchr, memcmp, memcpy): Add
>> 	read_only and write_only attributes.
>> 	(memset, rindex, stpcpy, stpncpy, strcasecmp, strcat): Same.
>> 	(strchr, strcmp, strcpy, strcspn, strdup, strndup, strlen): Same.
>> 	(strncasecmp, strncat, strncmp, strncpy, strrchr, strspn, strstr): Same.
>> 	(free, __memcpy_chk, __memmove_chk, __memset_chk): Same.
>> 	(__strcpy_chk, __strncpy_chk): Same.
>> 	* 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 (no_side_effect): Document new attribute.
>> 	(read_only, write_only, read_write): Same.
>> 	* tree-ssa-uninit.c (maybe_warn_uninit_accesss): New functions.
>> 	(warn_uninitialized_vars): Rename argument.  Factor out code into
>> 	maybe_warn_uninit_accesss.  Call it.
>>
>> gcc/c/ChangeLog:
>>
>> 	PR c/80806
>> 	PR middle-end/83859
>> 	* c-parser.c (c_parser::no_set_read, no_init, in_arg): New members.
>> 	(c_parser_expr_list): Add argument.
>> 	(c_parser_attribute): Pass a new argument to get_nonnull_operand.
>> 	(c_parser_initializer): Set parser->in_init.
>> 	(c_parser_binary_expression): Use parser->no_set_read.
>> 	(c_parser_unary_expression): Same.
>> 	(c_parser_sizeof_expression): Use parser->in_arg.
>> 	(c_parser_postfix_expression_after_primary): Adjust.
>> 	(is_write_only_p): New function.
>> 	(c_parser_expr_list): Add argument.
>> 	Avoid setting DECL_READ_P for decls passed to write-only function
>> 	arguments.
>> 	(c_parser_objc_keywordexpr): Pass a new argument to c_parser_expr_list.
>> 	(c_parser_oacc_wait_list): Same.
>> 	* c-tree.h (parser_build_binary_op): Add argument.
>> 	* c-typeck.c (default_conversion): Same.  Use it.
>> 	(parser_build_binary_op): Same.
>> 	(build_binary_op): Same.
>>
>> gcc/c-family/ChangeLog:
>>
>> 	PR c/80806
>> 	PR middle-end/83859
>> 	* c-attribs.c (handle_no_side_effect_attribute): New function.
>> 	(handle_read_only_attribute, handle_write_only_attribute): Same.
>> 	(handle_read_write_attribute): Same.
>> 	(c_common_attribute_table): Add new attributes.
>> 	(get_argument_type): New function.
>> 	(handle_rdwr_attributes): Same.
>> 	(has_attribute): Add argument to callback signature.  Pass to it
>> 	a new value.
>> 	(get_nonnull_operand): Rename...
>> 	(get_attribute_operand): ...to this.
>> 	* c-common.c (get_nonnull_operand): Rename...
>> 	(get_attribute_operand): ...to this.
>> 	(build_binary_op): Add a new argument.
>> 	(default_conversion): Same.
>> 	(has_attribute): Adjust argument type.
>>
>> gcc/cp/ChangeLog:
>>
>> 	PR c/80806
>> 	PR middle-end/83859
>> 	* typeck.c (cp_default_conversion): Add argument.
>> 	(build_binary_op): Same.
>>
>> gcc/testsuite/ChangeLog:
>>
>> 	PR c/80806
>> 	PR middle-end/83859
>> 	* c-c++-common/Wsizeof-pointer-memaccess1.c: Adjust.
>> 	* c-c++-common/Wsizeof-pointer-memaccess2.c: Adjust.
>> 	* gcc.dg/Wstrict-aliasing-bogus-vla-1.c: Adjust.
>> 	* gcc/testsuite/gcc.dg/Wunused-but-set-var.c: New test.
>> 	* gcc.dg/attr-alloc_size.c: Adjust.
>> 	* gcc/testsuite/gcc.dg/attr-read-only-2.c: New test.
>> 	* gcc/testsuite/gcc.dg/attr-read-only.c: New test.
>> 	* gcc/testsuite/gcc.dg/attr-write-only-2.c: New test.
>> 	* gcc/testsuite/gcc.dg/attr-write-only.c: New test.
>> 	* gcc.dg/nonnull-3.c: Adjust.
>> 	* gcc.dg/pr40340-2.c: Adjust.
>> 	* gcc.dg/pr78768.c: Adjust.
>> 	* gcc.dg/pr79715.c: Adjust.
>> 	* gcc.dg/tree-ssa/builtin-snprintf-7.c: Adjust.
>> 	* gcc/testsuite/gcc.dg/uninit-builtin.c: New test.
> You mention that this is almost done, but not yet finished.

It was not finished in the sense that I knew of a false positive
warning due to the C front-end change.  (I was also looking for
feedback on some of the design and implementation choices.)

   If I were
> working on this I could have split this up into adding the support for
> the attributes first without trying to use them anywhere first.  Then a
> separate patch that adds the attribute to the builtins, then a patch
> that exploited the new attributes to do something useful.
> 
> Please try to avoid creating large patches that are so easily broken up.
>   It just makes review harder than it needs to be and slows down getting
> the patches integrated.

I couldn't think of a way to test the new attributes without also
making other changes, but I've come up with one so I extracted just
the basic bits from it and I'm submitting those now.  I'll post
the rest as I wrap them up.

> Your ChangeLog references "no_side_effect" in a few places.  But I don't
> see any of those changes actually in the patch, except for adding it in
> builtin-attrs.def.  I'm guessing those are supposed to all be part of a
> follow-up patch?

Yes, no_side_effect is part of the optimization changes.  I have
removed mentions of it in the attached revision.

> 
> FWIW, I believe there may be a DSE case that your patches allow us to
> handle.  Essentially we had a case where DSE didn't fire because we
> didn't have detailed information about how the operands to one of the
> mem* or str* calls was used.  I don't expect you to own this missing opt
> as part of this submission.

That's right.  This is something I'd like to tackle for GCC 11.

> 
> 
>> diff --git a/gcc/attribs.h b/gcc/attribs.h
>> index 23a7321e04a..0e2320701b1 100644
>> --- a/gcc/attribs.h
>> +++ b/gcc/attribs.h
>> @@ -218,4 +218,36 @@ lookup_attribute_by_prefix (const char *attr_name, tree list)
>>       }
>>   }
>>   
>> +/* Description of a function argument declared attribute read_only,
>> +   read_write, or write_only.  Used as an "iterator" over all such
>> +   arguments in a function declaration or call.  */
>> +
>> +struct attr_access
>> +{
>> +  /* Attribute chain for the given function declaration.  */
>> +  tree attrs;
>> +
>> +  /* The attribute pointer argument.  */
>> +  tree ptr;
>> +  /* The size of the pointed-to object or NULL when not specified.  */
>> +  tree size;
>> +
>> +  /* The tree node corresponding to the current argument in the chain
>> +     of formal function arguments in a call to the given function.
>> +     Used by attributes that specify that relevant arguments are of
>> +     the given kind, as in
>> +       strcmp (const char*, const char*) __attribute__ ((read_only))  */
>> +  tree argchain;
>> +
>> +  /* The zero-based number of each of the formal function arguments.  */
>> +  unsigned ptrarg;
>> +  unsigned sizarg;
>> +  enum Kind { read_only, write_only, read_write };
> Nit: Mixed case "Kind" use either KIND or kind.

FWIW: I find either alternative makes the code harder, not easier
to read, and neither would make the code fully conform to the GNU
style (it asks to capitalize enum constants).  I've changed it
to "kind" (though I think it's fine either way and wish we would
dwell less on these things).

>> diff --git a/gcc/c/c-typeck.c b/gcc/c/c-typeck.c
>> index d4e12eb93d1..2e439c43b91 100644
>> --- a/gcc/c/c-typeck.c
>> +++ b/gcc/c/c-typeck.c
>> @@ -2146,14 +2146,30 @@ perform_integral_promotions (tree exp)
>>      In addition, manifest constants symbols are replaced by their values.  */
>>   
>>   tree
>> -default_conversion (tree exp)
>> +default_conversion (tree exp, bool read_p /* = true */)
> Please update the function comment to describe the new argument.  I
> think you need to do that for c_parser_expr_list and
> parser_build_binary_op and build_binary_op.
> 
> 
>> diff --git a/gcc/gimple.h b/gcc/gimple.h
>> index cf1f8da5ae2..4d850a088cf 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"
> How important is this (ie, how painful is it to have the .c/.cc files
> include function.h/basic-block.h?  We generally frown on having one
> header include others like this.

The header already has a few #include directives presumably to
allow the inline functions compile.  I get compilation errors
without the #include directives and haven't tried to solve them
differently.  I know you agree but I can't help but this as
an opportunity to vent: it's really a horrible practice not to
have headers stand on their own.  It causes so much frustration
whenever a header is included in a .c file that doesn't already
include all the prerequisite headers.  There's got to be a tool
out there that would fix this mess automatically for us.

>> diff --git a/gcc/tree-ssa-uninit.c b/gcc/tree-ssa-uninit.c
>> index fe8f8f0bc28..b9d455159cb 100644
>> --- a/gcc/tree-ssa-uninit.c
>> +++ b/gcc/tree-ssa-uninit.c
> Something else to consider (as a separate follow-up).
> 
> IIUC your code adds checking arguments at call sites which is a nice
> improvement.  There may be BZs related to this issue.
> 
> Another concept we might want to consider based on what I've seen pop up
> fairly often in code is the concept of "must write".  In the caller we'd
> consider passing a must-write object to a function call as
> initialization which would cut down on false positives.  We could verify
> the behavior in the callee.

This is pretty much what the write_only attribute does.  For
the purposes of -Wuninitialized, at the call site, the compiler
assumes that the object has been written to.  (I've removed this
from the revised patch.)  There is nothing to enforce that
the function actually does write into the object.  That could
be added when the -Wuninitialized bits are added back, or later
on.

> 
> Also, I think Fortran has the concept of "Intent" which does largely the
> same thing you're doing.  You might consider reaching out to the Fortran
> front-end folks and see if they can encode their Intent information into
> your attributes.  I believe there's BZs related to using Intent
> information to avoid false positives from the uninit warning pass.

Thanks for the suggestion.  I will do that for GCC 11.  I take
Richard's point that the attributes' semantics need to be clearly
and carefully specified before they're put to use for optimization.

> 
> I don't see anything terribly concerning.  Looking forward to the final
> iteration here.

Attached is a subset of the original patch that just adds the three
attributes and uses them to do buffer overflow checking.  I have
also enhanced the detection of invalid arguments (null pointers,
negative sizes).

Retested on x86_64-linux.

Martin

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

PR middle-end/83859 - Please add new attribute which will establish relation between parameters for buffer and its size

gcc/ChangeLog:

	PR middle-end/83859
	* builtin-attrs.def (ATTR_READ_ONLY): New.
	*(ATTR_READ_WRITE, ATTR_WRITE_ONLY): New.
	(ATTR_NOTHROW_WRONLY1_LEAF, ATTR_NOTHROW_WRONLY1_2_LEAF): New.
	(ATTR_NOTHROW_WRONLY1_3_LEAF, ATTR_NOTHROW_WRONLY2_3_LEAF): New.
	(ATTR_RET1_NOTHROW_WRONLY1_LEAF, ATTR_RET1_NOTHROW_WRONLY1_3_LEAF): New.
	(ATTR_RET1_NOTHROW_NONNULL_RDONLY2_LEAF): New.
	(ATTR_RET1_NOTHROW_NONNULL_RDWR1_RDONLY2_LEAF): New.
	(ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_3_LEAF): New.
	(ATTR_RET1_NOTHROW_WRONLY1_RDONLY2_LEAF): New.
	(ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_LEAF): New.
	(ATTR_MALLOC_NOTHROW_NONNULL_RDONLY1_LEAF): New.
	(ATTR_PURE_NOTHROW_NONNULL_RDONLY1_LEAF): New.
	(ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF): New.
	(ATTR_RETNONNULL_RDONLY2_3_NOTHROW_LEAF): New.
	(ATTR_RETNONNULL_WRONLY1_3_RDONLY2_3_NOTHROW_LEAF): New.
	(ATTR_PURE_NOTHROW_NONNULL_RDONLY1_3_LEAF): New.
	(ATTR_PURE_NOTHROW_NONNULL_RDONLY1_2_3_LEAF): New.
	(ATTR_RETNONNULL_RDONLY2_NOTHROW_LEAF): New.
	(ATTR_RETNONNULL_WRONLY1_RDONLY2_NOTHROW_LEAF): New.
	(ATTR_RETNONNULL_WRONLY1_3_RDONLY2_NOTHROW_LEAF): New.
	* builtins.c (check_access): Make extern.  Consistently set
	the no-warning bit after issuing a warning.
	* builtins.h (check_access): Declare.
	* builtins.def (bcopy, bzero, index, memchr, memcmp, memcpy): Add
	read_only and write_only attributes.
	(memset, rindex, stpcpy, stpncpy, strcasecmp, strcat): Same.
	(strchr, strcmp, strcpy, strcspn, strdup, strndup, strlen): Same.
	(strncasecmp, strncat, strncmp, strncpy, strrchr, strspn, strstr): Same.
	(free, __memcpy_chk, __memmove_chk, __memset_chk): Same.
	(__strcpy_chk, __strncpy_chk): Same.
	* 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 (read_only): Document new attribute.
	(write_only, read_write): Same.

gcc/c-family/ChangeLog:

	PR middle-end/83859
	* c-attribs.c (handle_read_only_attribute): New function.
	(handle_write_only_attribute): Same.
	(handle_read_write_attribute): Same.
	(c_common_attribute_table): Add new attributes.
	(get_argument_type): New function.
	(handle_rdwr_attributes): Same.
	(has_attribute): Add argument to callback signature.  Pass to it
	a new value.
	(get_nonnull_operand): Rename...
	(get_attribute_operand): ...to this.
	* c-common.c (get_nonnull_operand): Rename...
	(get_attribute_operand): ...to this.
	(build_binary_op): Add a new argument.
	(default_conversion): Same.
	(has_attribute): Adjust argument type.

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-read-only.c: New test.
	* gcc.dg/attr-write-only.c: New test.

diff --git a/gcc/attribs.c b/gcc/attribs.c
index b89be5834de..6a1cb3270d2 100644
--- a/gcc/attribs.c
+++ b/gcc/attribs.c
@@ -33,6 +33,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "diagnostic.h"
 #include "pretty-print.h"
 #include "intl.h"
+#include "gimple.h"
 
 /* Table of the tables of attributes (common, language, format, machine)
    searched.  */
@@ -2021,6 +2022,135 @@ maybe_diag_alias_attributes (tree alias, tree target)
     }
 }
 
+/* Given function type FNTYPE, initialize ACCESS based on the function's
+   first access attribute specifier, if one exists, or advance it to the
+   next specifier if it's already been initialized.  Return true if
+   ACCESS has been initialized or advanced to a valid access specifier,
+   false otherwise.  */
+
+bool
+get_attr_access_ptr_and_size (tree fntype, attr_access *access)
+{
+  if (!access->attrs)
+    access->attrs = TYPE_ATTRIBUTES (fntype);
+  else if (TREE_CODE (access->attrs) != TREE_LIST)
+    {
+      /* The last call sets ACCESS->ATTRS to something other than
+	 TREE_LIST to indicate it's reached the end.  */
+      return false;
+    }
+
+  if (!access->attrs)
+    return false;
+
+  const char *attr_name;
+  switch (access->kind)
+    {
+    case attr_access::read_only:
+      attr_name = "read_only";
+      break;
+
+    case attr_access::read_write:
+      attr_name = "read_write";
+      break;
+
+    case attr_access::write_only:
+      attr_name = "write_only";
+      break;
+
+    default:
+      gcc_unreachable ();
+    }
+
+  access->attrs = lookup_attribute (attr_name, access->attrs);
+  if (!access->attrs)
+    return false;
+
+  /* When operands are specified, iterate over them.  */
+  tree opers = TREE_VALUE (access->attrs);
+  if (opers)
+    access->ptrarg = TREE_INT_CST_LOW (TREE_VALUE (opers)) - 1;
+  else
+    {
+      /* When no operands are specified, the attribute applies to every
+	 pointer argument.  Use ARGCHAIN to find the next such argument
+	 if it exists.  */
+      if (access->argchain)
+	access->argchain = TREE_CHAIN (access->argchain);
+      else
+	{
+	  access->ptrarg = 0;
+	  access->argchain = TYPE_ARG_TYPES (fntype);
+	}
+
+      while (!POINTER_TYPE_P (TREE_VALUE (access->argchain)))
+	{
+	  access->argchain = TREE_CHAIN (access->argchain);
+	  if (!access->argchain)
+	    return false;
+	  ++access->ptrarg;
+	}
+    }
+
+  if (opers)
+    opers = TREE_CHAIN (opers);
+
+  if (opers)
+    access->sizarg = TREE_INT_CST_LOW (TREE_VALUE (opers)) - 1;
+  else
+    access->sizarg = UINT_MAX;
+
+  access->ptr = NULL_TREE;
+  access->size = NULL_TREE;
+  access->attrs = TREE_CHAIN (access->attrs);
+
+  /* When the last attribute/argument has been reached set ACCESS->ATTRS
+     to something other than TREE_LIST to indicate that to the next call.  */
+  if (!access->attrs && !access->argchain)
+    access->attrs = void_type_node;
+
+  return true;
+}
+
+/* Given a Gimple statement STMT that's a function call, initialize ACCESS
+   based on the called function's first access attribute specifier, if one
+   exists, or advance it to the next specifier if it's already been
+   initialized.  Return true if ACCESS has been initialized or advanced
+   to a valid access specifier, false otherwise.  */
+
+bool
+get_attr_access_ptr_and_size (const gimple *stmt, attr_access *access)
+{
+  const gcall *gc = as_a <const gcall *> (stmt);
+  if (!gc)
+    return false;
+
+  tree fntype = gimple_call_fntype (gc);
+  if (!fntype)
+    return false;
+
+  if (!get_attr_access_ptr_and_size (fntype, access))
+    return false;
+
+  if (access->ptrarg < gimple_call_num_args (gc))
+    {
+      tree ptr = gimple_call_arg (gc, access->ptrarg);
+      access->ptr = POINTER_TYPE_P (TREE_TYPE (ptr)) ? ptr : NULL_TREE;
+    }
+  else
+    access->ptr = NULL_TREE;
+
+  if (access->sizarg <= gimple_call_num_args (gc))
+    {
+      tree size = gimple_call_arg (gc, access->sizarg);
+      access->size = INTEGRAL_TYPE_P (TREE_TYPE (size)) ? size : NULL_TREE;
+    }
+  else
+    access->size = NULL_TREE;
+
+  return access->ptr;
+}
+
 
 #if CHECKING_P
 
diff --git a/gcc/attribs.h b/gcc/attribs.h
index 23a7321e04a..657216d6584 100644
--- a/gcc/attribs.h
+++ b/gcc/attribs.h
@@ -218,4 +218,37 @@ lookup_attribute_by_prefix (const char *attr_name, tree list)
     }
 }
 
+/* Description of a function argument declared attribute read_only,
+   read_write, or write_only.  Used as an "iterator" over all such
+   arguments in a function declaration or call.  */
+
+struct attr_access
+{
+  /* Attribute chain for the given function declaration.  */
+  tree attrs;
+
+  /* The attribute pointer argument.  */
+  tree ptr;
+  /* The size of the pointed-to object or NULL when not specified.  */
+  tree size;
+
+  /* The tree node corresponding to the current argument in the chain
+     of formal function arguments in a call to the given function.
+     Used by attributes that specify that all relevant pointer
+     arguments are of the given kind without explicitly referencing
+     any size arguments, as in:
+       strcmp (const char*, const char*) __attribute__ ((read_only))  */
+  tree argchain;
+
+  /* The zero-based number of each of the formal function arguments.  */
+  unsigned ptrarg;
+  unsigned sizarg;
+  enum access_kind { read_only, write_only, read_write };
+
+  access_kind kind;
+};
+
+extern bool get_attr_access_ptr_and_size (tree, attr_access *);
+extern bool get_attr_access_ptr_and_size (const gimple *, attr_access *);
+
 #endif // GCC_ATTRIBS_H
diff --git a/gcc/builtin-attrs.def b/gcc/builtin-attrs.def
index 39d1395f42a..9cc46899561 100644
--- a/gcc/builtin-attrs.def
+++ b/gcc/builtin-attrs.def
@@ -119,6 +119,9 @@ DEF_ATTR_IDENT (ATTR_TM_TMPURE, "transaction_pure")
 DEF_ATTR_IDENT (ATTR_RETURNS_TWICE, "returns_twice")
 DEF_ATTR_IDENT (ATTR_RETURNS_NONNULL, "returns_nonnull")
 DEF_ATTR_IDENT (ATTR_WARN_UNUSED_RESULT, "warn_unused_result")
+DEF_ATTR_IDENT (ATTR_READ_ONLY, "read_only")
+DEF_ATTR_IDENT (ATTR_READ_WRITE, "read_write")
+DEF_ATTR_IDENT (ATTR_WRITE_ONLY, "write_only")
 
 DEF_ATTR_TREE_LIST (ATTR_NOVOPS_LIST, ATTR_NOVOPS, ATTR_NULL, ATTR_NULL)
 
@@ -212,6 +215,7 @@ DEF_ATTR_TREE_LIST (ATTR_NOTHROW_NONNULL, ATTR_NONNULL, ATTR_NULL, \
 DEF_ATTR_TREE_LIST (ATTR_NOTHROW_NONNULL_LEAF, ATTR_NONNULL, ATTR_NULL, \
 			ATTR_NOTHROW_LEAF_LIST)
 DEF_ATTR_TREE_LIST (ATTR_NOTHROW_NONNULL_LEAF_LIST, ATTR_LEAF, ATTR_NULL, ATTR_NOTHROW_NONNULL_LEAF)
+
 /* Nothrow functions whose first parameter is a nonnull pointer.  */
 DEF_ATTR_TREE_LIST (ATTR_NOTHROW_NONNULL_1, ATTR_NONNULL, ATTR_LIST_1, \
 			ATTR_NOTHROW_LIST)
@@ -271,10 +275,54 @@ DEF_ATTR_TREE_LIST (ATTR_NOTHROW_NONNULL_TYPEGENERIC_LEAF,
 /* Nothrow const functions whose pointer parameter(s) are all nonnull.  */
 DEF_ATTR_TREE_LIST (ATTR_CONST_NOTHROW_NONNULL, ATTR_CONST, ATTR_NULL, \
 			ATTR_NOTHROW_NONNULL)
+
 /* Nothrow leaf functions whose pointer parameter(s) are all nonnull,
-   and which return their first argument.  */
+   which return their first argument, and which access their arguments
+   for reading and writing as indicated by RDONLY and WRONLY.  */
 DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_NONNULL_LEAF, ATTR_FNSPEC, ATTR_LIST_STR1, \
 			ATTR_NOTHROW_NONNULL_LEAF)
+
+DEF_ATTR_TREE_LIST (ATTR_NOTHROW_WRONLY1_LEAF, ATTR_WRITE_ONLY, \
+		    ATTR_LIST_1, ATTR_NOTHROW_LEAF_LIST)
+
+DEF_ATTR_TREE_LIST (ATTR_NOTHROW_WRONLY1_2_LEAF, ATTR_WRITE_ONLY, \
+		    ATTR_LIST_1_2, ATTR_NOTHROW_LEAF_LIST)
+
+DEF_ATTR_TREE_LIST (ATTR_NOTHROW_WRONLY1_3_LEAF, ATTR_WRITE_ONLY, \
+		    ATTR_LIST_1_3, ATTR_NOTHROW_LEAF_LIST)
+
+DEF_ATTR_TREE_LIST (ATTR_NOTHROW_WRONLY2_3_LEAF, ATTR_WRITE_ONLY, \
+		    ATTR_LIST_2_3, ATTR_NOTHROW_NONNULL_LEAF)
+
+DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_WRONLY1_LEAF, ATTR_WRITE_ONLY, \
+		    ATTR_LIST_1, ATTR_RET1_NOTHROW_NONNULL_LEAF)
+
+DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_WRONLY1_3_LEAF, ATTR_WRITE_ONLY, \
+		    ATTR_LIST_1_3, ATTR_RET1_NOTHROW_NONNULL_LEAF)
+
+/* strcat.  */
+DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_NONNULL_RDONLY2_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_2,
+		    ATTR_RET1_NOTHROW_NONNULL_LEAF)
+DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_NONNULL_RDWR1_RDONLY2_LEAF,
+		    ATTR_READ_WRITE, ATTR_LIST_1,
+		    ATTR_RET1_NOTHROW_NONNULL_RDONLY2_LEAF)
+
+/* memcpy, memmove.  */
+DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_3_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_2_3,
+		    ATTR_RET1_NOTHROW_WRONLY1_3_LEAF)
+
+/* strcpy.  */
+DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_WRONLY1_RDONLY2_LEAF,
+ 		    ATTR_READ_ONLY, ATTR_LIST_2,
+		    ATTR_RET1_NOTHROW_WRONLY1_LEAF)
+
+/* strncpy.  */
+DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_2,
+		    ATTR_RET1_NOTHROW_WRONLY1_3_LEAF)
+
 /* Nothrow leaf functions whose pointer parameter(s) are all nonnull,
    and return value is also nonnull.  */
 DEF_ATTR_TREE_LIST (ATTR_RETNONNULL_NOTHROW_LEAF, ATTR_RETURNS_NONNULL, ATTR_NULL, \
@@ -305,6 +353,52 @@ DEF_ATTR_TREE_LIST (ATTR_WARN_UNUSED_RESULT_NOTHROW_NONNULL_LEAF, ATTR_WARN_UNUS
 DEF_ATTR_TREE_LIST (ATTR_MALLOC_WARN_UNUSED_RESULT_NOTHROW_NONNULL_LEAF, ATTR_MALLOC, ATTR_NULL, \
 			ATTR_WARN_UNUSED_RESULT_NOTHROW_NONNULL_LEAF)
 
+/* strdup, strndup.  */
+DEF_ATTR_TREE_LIST (ATTR_MALLOC_NOTHROW_NONNULL_RDONLY1_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_1,
+		    ATTR_MALLOC_WARN_UNUSED_RESULT_NOTHROW_NONNULL_LEAF)
+
+/* strchr, strlen. */
+DEF_ATTR_TREE_LIST (ATTR_PURE_NOTHROW_NONNULL_RDONLY1_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_1,
+		    ATTR_PURE_NOTHROW_NONNULL_LEAF)
+
+/* strcmp, strstr. */
+DEF_ATTR_TREE_LIST (ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_2,
+		    ATTR_PURE_NOTHROW_NONNULL_RDONLY1_LEAF)
+
+/* mempcpy.  */
+DEF_ATTR_TREE_LIST (ATTR_RETNONNULL_RDONLY2_3_NOTHROW_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_2_3,
+		    ATTR_RETNONNULL_NOTHROW_LEAF)
+DEF_ATTR_TREE_LIST (ATTR_RETNONNULL_WRONLY1_3_RDONLY2_3_NOTHROW_LEAF,
+		    ATTR_WRITE_ONLY, ATTR_LIST_1_3,
+		    ATTR_RETNONNULL_RDONLY2_3_NOTHROW_LEAF)
+
+/* memchr.  */
+DEF_ATTR_TREE_LIST (ATTR_PURE_NOTHROW_NONNULL_RDONLY1_3_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_1_3,
+		    ATTR_NOTHROW_NONNULL_LEAF)
+
+/* memcmp.  */
+DEF_ATTR_TREE_LIST (ATTR_PURE_NOTHROW_NONNULL_RDONLY1_2_3_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_2_3, \
+		    ATTR_PURE_NOTHROW_NONNULL_RDONLY1_3_LEAF)
+
+/* stpcpy.  */
+DEF_ATTR_TREE_LIST (ATTR_RETNONNULL_RDONLY2_NOTHROW_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_2,
+		    ATTR_RETNONNULL_NOTHROW_LEAF)
+DEF_ATTR_TREE_LIST (ATTR_RETNONNULL_WRONLY1_RDONLY2_NOTHROW_LEAF,
+		    ATTR_WRITE_ONLY, ATTR_LIST_1,
+		    ATTR_RETNONNULL_RDONLY2_NOTHROW_LEAF)
+
+/* stpncpy.  */
+DEF_ATTR_TREE_LIST (ATTR_RETNONNULL_WRONLY1_3_RDONLY2_NOTHROW_LEAF,
+		    ATTR_READ_ONLY, ATTR_LIST_2,
+		    ATTR_RETNONNULL_RDONLY2_NOTHROW_LEAF)
+
 /* Construct a tree for the format attribute (and implicitly nonnull).  */
 #define DEF_FORMAT_ATTRIBUTE(TYPE, FA, VALUES)				 \
   DEF_ATTR_TREE_LIST (ATTR_##TYPE##_##VALUES, ATTR_NULL,		 \
diff --git a/gcc/builtins.c b/gcc/builtins.c
index f94151bd84d..e300cdb649d 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)
 {
@@ -3433,37 +3433,42 @@ 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 = 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);
 	    }
 	  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 = 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);
 	  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 = 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);
 	    }
 	  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 = 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);
+	  if (warned)
+	    TREE_NO_WARNING (exp) = true;
 
 	  /* Return error when an overflow has been detected.  */
 	  return false;
@@ -3486,21 +3491,26 @@ 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 = warning_at (loc, opt,
+				     "%K%qD specified bound %E "
+				     "exceeds maximum object size %E",
+				     exp, func,
+				     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 = warning_at (loc, opt,
+				     "%K%qD specified bound between %E and %E "
+				     "exceeds maximum object size %E",
+				     exp, func,
+				     range[0], range[1], maxobjsize);
+
+	      if (warned)
+		TREE_NO_WARNING (exp) = true;
 
 	      return false;
 	    }
@@ -3510,18 +3520,23 @@ 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 = warning_at (loc, opt,
+				     "%K%qD specified bound %E "
+				     "exceeds destination size %E",
+				     exp, func,
+				     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 = warning_at (loc, opt,
+				     "%K%qD specified bound between %E and %E "
+				     "exceeds destination size %E",
+				     exp, func,
+				     range[0], range[1], dstsize);
+	      if (warned)
+		TREE_NO_WARNING (exp) = true;
+
 	      return false;
 	    }
 	}
@@ -3536,26 +3551,31 @@ 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 = 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);
       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 = warning_at (loc, opt,
+			       "%K%qD reading %E or more bytes from a region "
+			       "of size %E",
+			       exp, func, 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 = 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);
+      if (warned)
+	TREE_NO_WARNING (exp) = true;
+
       return false;
     }
 
diff --git a/gcc/builtins.def b/gcc/builtins.def
index d8233f5f760..e24cd31e7ca 100644
--- a/gcc/builtins.def
+++ b/gcc/builtins.def
@@ -722,9 +722,9 @@ DEF_LIB_BUILTIN        (BUILT_IN_STRNCMP, "strncmp", BT_FN_INT_CONST_STRING_CONS
 DEF_LIB_BUILTIN        (BUILT_IN_STRNCPY, "strncpy", BT_FN_STRING_STRING_CONST_STRING_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRNLEN, "strnlen", BT_FN_SIZE_CONST_STRING_SIZE, ATTR_PURE_NOTHROW_NONNULL_LEAF)
 DEF_LIB_BUILTIN        (BUILT_IN_STRPBRK, "strpbrk", BT_FN_STRING_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN        (BUILT_IN_STRRCHR, "strrchr", BT_FN_STRING_CONST_STRING_INT, ATTR_PURE_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN        (BUILT_IN_STRSPN, "strspn", BT_FN_SIZE_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_LEAF)
-DEF_LIB_BUILTIN        (BUILT_IN_STRSTR, "strstr", BT_FN_STRING_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_LEAF)
+DEF_LIB_BUILTIN        (BUILT_IN_STRRCHR, "strrchr", BT_FN_STRING_CONST_STRING_INT, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_LEAF)
+DEF_LIB_BUILTIN        (BUILT_IN_STRSPN, "strspn", BT_FN_SIZE_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF)
+DEF_LIB_BUILTIN        (BUILT_IN_STRSTR, "strstr", BT_FN_STRING_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF)
 
 /* Category: stdio builtins.  */
 DEF_LIB_BUILTIN        (BUILT_IN_FPRINTF, "fprintf", BT_FN_INT_FILEPTR_CONST_STRING_VAR, ATTR_NONNULL_1_FORMAT_PRINTF_2_3)
@@ -870,7 +870,7 @@ DEF_EXT_LIB_BUILTIN    (BUILT_IN_FFSLL, "ffsll", BT_FN_INT_LONGLONG, ATTR_CONST_
 DEF_EXT_LIB_BUILTIN        (BUILT_IN_FORK, "fork", BT_FN_PID, ATTR_NOTHROW_LIST)
 DEF_GCC_BUILTIN        (BUILT_IN_FRAME_ADDRESS, "frame_address", BT_FN_PTR_UINT, ATTR_NULL)
 /* [trans-mem]: Adjust BUILT_IN_TM_FREE if BUILT_IN_FREE is changed.  */
-DEF_LIB_BUILTIN        (BUILT_IN_FREE, "free", BT_FN_VOID_PTR, ATTR_NOTHROW_LEAF_LIST)
+DEF_LIB_BUILTIN        (BUILT_IN_FREE, "free", BT_FN_VOID_PTR, ATTR_NOTHROW_WRONLY1_LEAF)
 DEF_GCC_BUILTIN        (BUILT_IN_FROB_RETURN_ADDR, "frob_return_addr", BT_FN_PTR_PTR, ATTR_NULL)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_GETTEXT, "gettext", BT_FN_STRING_CONST_STRING, ATTR_FORMAT_ARG_1)
 DEF_C99_BUILTIN        (BUILT_IN_IMAXABS, "imaxabs", BT_FN_INTMAX_INTMAX, ATTR_CONST_NOTHROW_LEAF_LIST)
@@ -967,16 +967,16 @@ DEF_BUILTIN_STUB (BUILT_IN_STRNCMP_EQ, "__builtin_strncmp_eq")
 
 /* Object size checking builtins.  */
 DEF_GCC_BUILTIN	       (BUILT_IN_OBJECT_SIZE, "object_size", BT_FN_SIZE_CONST_PTR_INT, ATTR_PURE_NOTHROW_LEAF_LIST)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_MEMCPY_CHK, "__memcpy_chk", BT_FN_PTR_PTR_CONST_PTR_SIZE_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_MEMMOVE_CHK, "__memmove_chk", BT_FN_PTR_PTR_CONST_PTR_SIZE_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_MEMCPY_CHK, "__memcpy_chk", BT_FN_PTR_PTR_CONST_PTR_SIZE_SIZE, ATTR_RET1_NOTHROW_WRONLY1_3_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_MEMMOVE_CHK, "__memmove_chk", BT_FN_PTR_PTR_CONST_PTR_SIZE_SIZE, ATTR_RET1_NOTHROW_WRONLY1_3_LEAF)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_MEMPCPY_CHK, "__mempcpy_chk", BT_FN_PTR_PTR_CONST_PTR_SIZE_SIZE, ATTR_RETNONNULL_NOTHROW_LEAF)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_MEMSET_CHK, "__memset_chk", BT_FN_PTR_PTR_INT_SIZE_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
+DEF_EXT_LIB_BUILTIN   (BUILT_IN_MEMSET_CHK, "__memset_chk", BT_FN_PTR_PTR_INT_SIZE_SIZE, ATTR_RET1_NOTHROW_WRONLY1_3_LEAF)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_STPCPY_CHK, "__stpcpy_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE, ATTR_RETNONNULL_NOTHROW_LEAF)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_STPNCPY_CHK, "__stpncpy_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE_SIZE, ATTR_RETNONNULL_NOTHROW_LEAF)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRCAT_CHK, "__strcat_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRCPY_CHK, "__strcpy_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRCPY_CHK, "__strcpy_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE, ATTR_RET1_NOTHROW_WRONLY1_LEAF)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRNCAT_CHK, "__strncat_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
-DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRNCPY_CHK, "__strncpy_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF)
+DEF_EXT_LIB_BUILTIN    (BUILT_IN_STRNCPY_CHK, "__strncpy_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE_SIZE, ATTR_RET1_NOTHROW_WRONLY1_3_LEAF)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_SNPRINTF_CHK, "__snprintf_chk", BT_FN_INT_STRING_SIZE_INT_SIZE_CONST_STRING_VAR, ATTR_FORMAT_PRINTF_NOTHROW_5_6)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_SPRINTF_CHK, "__sprintf_chk", BT_FN_INT_STRING_INT_SIZE_CONST_STRING_VAR, ATTR_NOTHROW_NONNULL_1_FORMAT_PRINTF_4_5)
 DEF_EXT_LIB_BUILTIN    (BUILT_IN_VSNPRINTF_CHK, "__vsnprintf_chk", BT_FN_INT_STRING_SIZE_INT_SIZE_CONST_STRING_VALIST_ARG, ATTR_FORMAT_PRINTF_NOTHROW_5_0)
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..eff430e267e 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -125,6 +125,10 @@ 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_read_only_attribute (tree *, tree, tree, int, bool *);
+static tree handle_read_write_attribute (tree *, tree, tree, int, bool *);
+static tree handle_write_only_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 +484,12 @@ 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 },
+  { "read_only",              0, 2, false, true, true, false,
+			      handle_read_only_attribute, NULL },
+  { "write_only",             0, 2, false, true, true, false,
+			      handle_write_only_attribute, NULL },
+  { "read_write",             0, 2, false, true, true, false,
+			      handle_read_write_attribute, NULL },
   { NULL,                     0, 0, false, false, false, false, NULL, NULL }
 };
 
@@ -3798,6 +3808,228 @@ 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 ( ; ; ++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;
+}
+
+/* Handle attributes read_only, write_only, and read_write.  */
+
+static tree
+handle_rdwr_attributes (bool writeable, tree *node, tree name,
+			tree operands, int ARG_UNUSED (flags),
+			bool *no_add_attrs)
+{
+  tree type = *node;
+  tree attrs = TYPE_ATTRIBUTES (type);
+
+  /* If no operands are specified, all non-const pointer arguments are
+     treated as write-only.  Verify a full prototype is given so that
+     the arguments will have the correct types when we actually check
+     them later.  Avoid diagnosing type-generic built-ins since those
+     have no prototype.  */
+  if (!operands
+      && !prototype_p (type)
+      && (!attrs || !lookup_attribute ("type generic", attrs)))
+    {
+      error ("attribute %qE without arguments on a non-prototype", name);
+      *no_add_attrs = true;
+      return NULL_TREE;
+    }
+
+  /* Attribute operands have been specified.  Verify that each operand
+     value references a non-const pointer argument to the function.  */
+  tree idxnodes[2] = { NULL_TREE, NULL_TREE };
+  tree argtypes[2] = { NULL_TREE, NULL_TREE };
+  unsigned HOST_WIDE_INT idxs[2] = { 0, 0 };
+
+  /* Number of function formal arguments (used in diagnostics).  */
+  unsigned argcount = 0;
+  /* Number of attribute operands.  */
+  unsigned opcount = 0;
+
+  for (unsigned i = 0; i != 2; ++i, operands = TREE_CHAIN (operands), ++opcount)
+    {
+      if (!operands)
+	break;
+
+      idxnodes[i] = TREE_VALUE (operands);
+
+      if (TREE_CODE (idxnodes[i]) != IDENTIFIER_NODE
+	  && TREE_CODE (idxnodes[i]) != FUNCTION_DECL)
+	idxnodes[i] = default_conversion (idxnodes[i]);
+
+      if (!get_attribute_operand (idxnodes[i], idxs + i))
+	{
+	  *no_add_attrs = true;
+	  idxs[i] = i + 1;
+	}
+
+      argtypes[i] = get_argument_type (type, idxs[i], &argcount);
+    }
+
+  if (*no_add_attrs)
+    {
+      /* Dignose an invalid operand and bail.  */
+      if (idxnodes[1])
+	error ("attribute %<%E(%E, %E)%> invalid operand %wu",
+	       name, idxnodes[0], idxnodes[1], idxs[0] ? idxs[0] : idxs[1]);
+      else
+	error ("attribute %<%E(%E)%> invalid operand",
+	       name, idxnodes[0]);
+
+      return NULL_TREE;
+    }
+
+  if (!argtypes[0] || (idxnodes[1] && !argtypes[1]))
+    {
+      if (idxnodes[1])
+	error ("attribute %<%E(%E, %E)%> operand exceeds number "
+	       "of arguments %u",
+	       name, idxnodes[0], idxnodes[1], argcount);
+      else if (idxnodes[0])
+	error ("attribute %<%E(%E)%> operand exceeds number of arguments %u",
+	       name, idxnodes[0], argcount);
+      else if (!opcount)
+	{
+	  /* FIXME: reject empty attribute write_only on function
+	     declarations with no non-const object pointer arguments.  */
+	  tree arg;
+	  function_args_iterator it;
+	  FOREACH_FUNCTION_ARGS (*node, arg, it)
+	    if (POINTER_TYPE_P (arg))
+	      return NULL_TREE;
+
+	  error ("attribute %<%E%> on a function with no pointer arguments",
+		 name);
+	  *no_add_attrs = true;
+	  return NULL_TREE;
+	}
+      else
+	error ("attribute %<%E%> invalid operand",
+	       name);
+
+      *no_add_attrs = true;
+      return NULL_TREE;
+    }
+
+  if (TREE_CODE (argtypes[0]) != POINTER_TYPE)
+    {
+      if (idxnodes[1])
+	error ("attribute %<%E(%E, %E)%> first operand references "
+	       "non-pointer argument type %qT",
+	       name, idxnodes[0], idxnodes[1], argtypes[0]);
+      else
+	error ("attribute %<%E(%E)%> references non-pointer argument "
+	       "type %qT",
+	       name, idxnodes[0], argtypes[0]);
+      *no_add_attrs = true;
+      return NULL_TREE;
+    }
+
+  if (writeable)
+    {
+      /* A read_write and write_only attributes must reference non-const
+	 arguments.  */
+      if (TYPE_READONLY (TREE_TYPE (argtypes[0])))
+	{
+	  if (idxnodes[1])
+	    error ("attribute %<%E(%E, %E)%> first operand references "
+		   "%qs-qualified argument type %qT",
+		   name, idxnodes[0], idxnodes[1], "const", argtypes[0]);
+	  else
+	    error ("attribute %<%E(%E)%> operand references %qs-qualified "
+		   "argument type %qT",
+		   name, idxnodes[0], "const", argtypes[0]);
+	  *no_add_attrs = true;
+	  return NULL_TREE;
+	}
+    }
+  else if (!TYPE_READONLY (TREE_TYPE (argtypes[0])))
+    {
+      /* A read_only attributes should reference const-qualified arguments
+	 but it's not an error if one doesn't.  */
+      if (idxnodes[1])
+	warning (OPT_Wattributes,
+		 "attribute %<%E(%E, %E)%> first operand references "
+		 "non-%qs argument type %qT",
+		 name, idxnodes[0], idxnodes[1], "const", argtypes[0]);
+      else
+	warning (OPT_Wattributes,
+		 "attribute %<%E(%E)%> operand references non-%qs "
+		 "argument type %qT",
+		 name, idxnodes[0], "const", argtypes[0]);
+    }
+
+  if (argtypes[1] && !INTEGRAL_TYPE_P (argtypes[1]))
+    {
+      if (idxnodes[1])
+	error ("attribute %<%E(%E, %E)%> second operand references "
+	       "non-integer argument type %qT",
+	       name, idxnodes[0], idxnodes[1], argtypes[1]);
+      else
+	error ("attribute %<%E(%E)%> second operand references "
+	       "non-integer argument type %qT",
+	       name, idxnodes[0], argtypes[1]);
+      *no_add_attrs = true;
+      return NULL_TREE;
+    }
+
+  return NULL_TREE;
+}
+
+/* Handle attribute read_only ([ptrarg [, sizarg]]).  */
+
+static tree
+handle_read_only_attribute (tree *node, tree name, tree operands, int flags,
+			    bool *no_add_attrs)
+{
+  return handle_rdwr_attributes (false, node, name, operands, flags,
+				 no_add_attrs);
+}
+
+/* Handle attribute write_only ([ptrarg [, sizarg]]).  */
+
+static tree
+handle_write_only_attribute (tree *node, tree name, tree operands, int flags,
+			     bool *no_add_attrs)
+{
+  return handle_rdwr_attributes (true, node, name, operands, flags,
+				 no_add_attrs);
+}
+
+/* Handle attribute read_write ([ptrarg [, sizarg]]).  */
+
+static tree
+handle_read_write_attribute (tree *node, tree name,
+			     tree operands, int flags, bool *no_add_attrs)
+{
+  return handle_rdwr_attributes (true, node, name, operands, flags,
+				 no_add_attrs);
+}
+
 /* 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..316a2a1281c 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"
@@ -1870,6 +1872,273 @@ 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 read_only,
+   write_only, or read_write operand and the read/write specification.  */
+typedef hash_map<rdwr_access_hash, attr_access> rdwr_map;
+
+/* Initialize a mapping for a call to function FNDECL declared with
+   attributes read_only, write_only, or read_write.  Each attribute
+   operand inserts one entry into the mapping with the operand number
+   as the key.  */
+
+static void
+init_attr_rdwr_indices (rdwr_map *rwm, tree fndecl)
+{
+  if (!fndecl)
+    return;
+
+  /* If the function's type has no attributes there's nothing to do.  */
+  tree fntype = TREE_TYPE (fndecl);
+  if (!TYPE_ATTRIBUTES (fntype))
+    return;
+
+  /* Iterate over arguments to functions declared with attribute
+     read_only or read_write and initialize those that are not
+     initialized.  */
+  const attr_access::access_kind access_kinds[] = {
+    attr_access::read_only, attr_access::read_write, attr_access::write_only
+  };
+
+  for (unsigned i = 0; i != sizeof access_kinds / sizeof *access_kinds; ++i)
+    {
+      attr_access acc = { };
+      acc.kind = access_kinds[i];
+      while (get_attr_access_ptr_and_size (fntype, &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);
+	}
+    }
+}
+
+/* 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.  */
+
+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.kind == attr_access::read_only
+       ? "read_only"
+       : (access.second.kind == 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 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)
+{
+  /* 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;
+
+  const tree fndecl = TREE_OPERAND (CALL_EXPR_FN (exp), 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
+	 first and (optionally) second operands.  When both operands 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 (TREE_TYPE (fndecl), 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];
+      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.kind == 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.kind == 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;
+
+  inform (DECL_SOURCE_LOCATION (fndecl),
+	  "in a call to function %qD declared with attribute %qs",
+	  fndecl, 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 +2255,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, fndecl);
+
   /* I counts args in order (to be) pushed; ARGPOS counts in order written.  */
   for (argpos = 0; argpos < num_actuals; i--, argpos++)
     {
@@ -2226,6 +2500,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 +2528,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..14218dbf53c 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -3850,6 +3850,104 @@ At present, a declaration to which @code{weakref} is attached can
 only be @code{static}.
 
 
+@item read_only
+@itemx read_only
+@itemx read_only (@var{arg-index})
+@itemx read_only (@var{ref-index}, @var{size-index})
+@cindex @code{read_only} function attribute
+The @code{read_only} attribute specifies that a function whose by-reference
+arguments of @code{const}-qualified types denoted by @var{ref-index} (or
+all such arguments if no @var{ref-index} is specified) are only used to
+read from the objects referenced by the argument but not write to them.
+Without the @var{size-index} operand, the pointer argument must be either
+null or point to an array of at least one object of the pointed-to type.
+Passing in the address of an uninitialized object is undefined.  The same
+pointer argument can appear as an operand to at most one @code{read_only},
+@code{write_only}, or @code{read_write_only} attribute.
+
+For example, the declaration below indicates that the @code{get_time} function
+only reads the @code{struct tm} object pointed-to by the argument but doesn't
+modify it.  Casting away the constness and modifying the argument is undefined.
+This information may be relied on to emit more efficient code at the call site,
+or by the @option{-Wuninitilized} option to detect reads of uninitialized
+variables.  Built-in functions such as @code{strlen} declare their const
+pointer arguments with the @code{read_only} attribute.
+
+@smallexample
+time_t get_time (const struct tm *) __attribute__ ((read_only));
+@end smallexample
+
+The @var{size-index} argument optionally specifies the number of elements
+of the array referenced by @var{ref-index} the function may read.  For
+references to objects of an incomplete type including @code{void}, the size
+of the array element type is taken to be one.  Passing in an array with
+fewer elements is diagnosed by @option{-Wstringop-overflow}.
+For example, the declaration below indicates that the function reads as many
+elements of the array referenced by the first argument as specified by
+the second argument.
+
+@smallexample
+int sum (const int *, unsigned) __attribute__ ((read_only (1, 2)));
+@end smallexample
+
+
+@item read_write
+@itemx read_write
+@itemx read_write (@var{arg-index})
+@itemx read_write (@var{ref-index}, @var{size-index})
+@cindex @code{read_write} function attribute
+The @code{read_write} attribute specifies that a function whose by-reference
+arguments denoted by @var{ref-index} (or all such arguments if no
+@var{ref-index} is specified) are used to both read from the objects
+referenced by the argument and to write to them.  Without the @var{size-index}
+operand, the pointer argument must be either null or point to an array of
+at least one object of the pointed-to type. Passing in the address of
+an uninitialized object is undefined.  The attribute is effectively
+the union of the @code{read_only} and @code{write_only} attributes.
+The same pointer argument can appear as an operand to at most one
+@code{read_only}, @code{write_only}, or @code{read_write_only} attribute.
+
+@item write_only
+@itemx write_only
+@itemx write_only (@var{arg-index})
+@itemx write_only (@var{ref-index}, @var{size-index})
+@cindex @code{write_only} function attribute
+The @code{write_only} attribute specifies that a function whose by-reference
+arguments of non-@code{const}-qualified types denoted by @var{ref-index} (or
+all such arguments if no @var{ref-index} is specified) are only used to write
+into the objects referenced by the argument but not read from it.
+Without the @var{size-index} operand, the pointer argument must be either
+null or point to an array of sufficient size and alignment for at least
+one object of the pointed-to type.  The same pointer argument can appear
+as an operand to at most one @code{read_only}, @code{write_only}, or
+@code{read_write_only} attribute.
+
+For example, the declaration below indicates that the @code{init_tm} function
+only writes to the @code{struct tm} object pointed-to by the argument but
+does not read from it.  This information may be relied on to emit more
+efficient code at the call site, and diagnose initialized but otherwise
+unused objects by one of the @option{-Wunused} options.  Built-in functions
+such as @code{memset} and @code{strcpy} declare their non-const pointer
+argument with the @code{write_only} attribute.
+
+@smallexample
+int init_tm (struct tm *) __attribute__ ((write_only));
+@end smallexample
+
+The @var{size-index} argument optionally specifies the number of elements
+of the array referenced by @var{ref-index} the function may write.  For
+references to objects of an incomplete type including @code{void}, the size
+of the array element type is taken to be one.  Passing in an array with fewer
+elements is diagnosed by @option{-Wstringop-overflow}.
+For example, the declaration below indicates that the function writes as many
+elements of the array referenced by the first argument as specified by
+the second argument.
+
+@smallexample
+void init (int *, unsigned, int) __attribute__ ((write_only (1, 2)));
+@end smallexample
+
+
 @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..183876a1c12
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-22.c
@@ -0,0 +1,175 @@
+/* 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 attributes
+   read_only and write_only 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__ ((read_only (__VA_ARGS__)))
+#define WRONLY(...)  __attribute__ ((write_only (__VA_ARGS__)))
+#define RDWR(...)    __attribute__ ((read_write (__VA_ARGS__)))
+
+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 any operands.  */
+
+RDONLY () 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);
+
+  int i = 0;
+  rd1_int (&i);
+
+  rd1_int ((int*)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 () 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" }
+}
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..e104d81b040
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-23.c
@@ -0,0 +1,153 @@
+/* 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 attributes
+   read_only and write_only 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__ ((read_only (__VA_ARGS__)))
+#define WRONLY(...)  __attribute__ ((write_only (__VA_ARGS__)))
+#define RDWR(...)  __attribute__ ((read_write (__VA_ARGS__)))
+
+/* 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 }
+}
diff --git a/gcc/testsuite/gcc.dg/attr-read-only.c b/gcc/testsuite/gcc.dg/attr-read-only.c
new file mode 100644
index 00000000000..5f55b8b3a7c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/attr-read-only.c
@@ -0,0 +1,65 @@
+/* PR middle-end/83859 - attribute to establish relation between parameters
+   for buffer and its size
+   Test to verify the handling of attribute read_only syntax.
+   { dg-do compile }
+   { dg-options "-Wall -ftrack-macro-expansion=0" } */
+
+#define RDONLY(...)  __attribute__ ((read_only (__VA_ARGS__)))
+
+int  __attribute__ ((read_only))
+rdonly_v_all (void);   /* { dg-error "attribute .read_only. on a function with no pointer arguments" } */
+
+int  __attribute__ ((read_only))
+rdonly_i_all (int);   /* { dg-error "attribute .read_only. on a function with no pointer arguments" } */
+
+int  __attribute__ ((read_only))
+rdonly_pi_all (const int*);
+
+int  __attribute__ ((read_only))
+rdonly_pv_pi_all (const void*, const int*);
+
+int  __attribute__ ((read_only))
+rdonly_i_pc_i_pd_all (int, const char*, int, const double*);
+
+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 .read_only\\(1, 1\\). second operand references non-integer argument type .const void *." } */
+
+int  RDONLY (1, 2)
+rdonly_pcv_pc_1_2 (const void*, char*);   /* { dg-error "attribute .read_only\\(1, 2\\). second operand references non-integer argument type .char *." } */
+
+int  RDONLY (2, 1)
+rdonly_pcd_pcv_2_1 (const double*, const void*);   /* { dg-error "attribute .read_only\\(2, 1\\). second operand references non-integer argument type .const double *." } */
+
+int __attribute__ ((read_only))
+rdonly_pcv_pcv_all (const void*, const void*);
+
+int RDONLY (2, 2)
+rdonly_pi_pcv_2_2 (int*, const void*);   /* { dg-error "second operand references non-integer argument type .const void *." } */
+
+int RDONLY (4)
+rdonly_i_i_i_4 (int, int, int);   /* { dg-error "attribute .read_only\\(4\\). operand exceeds number of arguments 3" } */
+
+int RDONLY (1)
+rdonly_i_1 (int);   /* { dg-error "attribute .read_only\\(1\\). references non-pointer argument type .int." } */
+
+int RDONLY (2)
+rdonly_i_pc (int, char*);   /* { dg-warning "attribute .read_only\\(2\\). operand references non-.const. argument type .char \\\*." } */
+
+int RDONLY (-1)
+rdonly_pcv_m1 (const void*);   /* { dg-error "attribute .read_only\\(-1\\). invalid operand" } */
+
+int RDONLY ("blah")
+rdonly_pcv_str (const void*);   /* { dg-error "attribute .read_only\\(\"blah\"\\). invalid operand" } */
diff --git a/gcc/testsuite/gcc.dg/attr-write-only.c b/gcc/testsuite/gcc.dg/attr-write-only.c
new file mode 100644
index 00000000000..27db9c17ce7
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/attr-write-only.c
@@ -0,0 +1,51 @@
+/* Test to verify the handling of attribute write_only syntax.
+   { dg-do compile }
+   { dg-options "-Wall -ftrack-macro-expansion=0" } */
+
+#define WRONLY(...)  __attribute__ ((write_only (__VA_ARGS__)))
+
+void  __attribute__ ((write_only))
+wronly_v_all (void);   /* { dg-error "attribute .write_only. on a function with no pointer arguments" } */
+
+void  __attribute__ ((write_only))
+wronly_i_all (int);   /* { dg-error "attribute .write_only. on a function with no pointer arguments" } */
+
+void wronly_v_1 (void*) WRONLY (1);
+void wronly_iv_2 (int, void*) WRONLY (2);
+void wronly_iiv_3 (int, int, void*) WRONLY (3);
+
+void wronly_v_0p1 (void*) WRONLY (0 + 1);
+void wronly_v_2m1 (void*) WRONLY (2 - 1);
+
+void WRONLY (1, 1)
+wronly_v_1_1 (void*);      /* { dg-error "attribute .write_only\\(1, 1\\). second operand references non-integer argument type .void *." } */
+
+void  WRONLY (1, 2)
+wronly_vv_1_2 (void*, void*);   /* { dg-error "attribute .write_only\\(1, 2\\). second operand references non-integer argument type .void *." } */
+
+void  WRONLY (2, 1)
+wronly_vv_2_1 (void*, void*);   /* { dg-error "attribute .write_only\\(2, 1\\). second operand references non-integer argument type .void *." } */
+
+void  __attribute__ ((write_only))
+wronly_vv_all (void*, void*);
+
+void  WRONLY (1, 1)
+wronly_vv_1_1 (void*, void*);   /* { dg-error "attribute .write_only\\(1, 1\\). second operand references non-integer argument type .void *." } */
+
+void WRONLY (2, 2)
+wronly_vv_1_1 (void*, void*);   /* { dg-error "second operand references non-integer argument type .void *." } */
+
+void WRONLY (4)
+wronly_4_exceed (int, int, int);   /* { dg-error "attribute .write_only\\(4\\). operand exceeds number of arguments 3" } */
+
+void WRONLY (1)
+wronly_1_i (int);   /* { dg-error "attribute .write_only\\(1\\). references non-pointer argument type .int." } */
+
+void WRONLY (2)
+wronly_2_cst (int, const char*);   /* { dg-error "attribute .write_only\\(2\\). operand references .const.-qualified argument type .const char *." } */
+
+void WRONLY (-1)
+wronly_m1_i (void*);    /* { dg-error "attribute .write_only\\(-1\\). invalid operand" } */
+
+void WRONLY ("blah")
+wronly_str_i (void*);   /* { dg-error "attribute .write_only\\(\"blah\"\\). invalid operand" } */

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

* Re: [WIP PATCH] add object access attributes (PR 83859)
  2019-11-15 21:41   ` Martin Sebor
@ 2019-11-18  9:00     ` Richard Biener
  2019-11-18 16:46       ` Martin Sebor
  0 siblings, 1 reply; 19+ messages in thread
From: Richard Biener @ 2019-11-18  9:00 UTC (permalink / raw)
  To: Martin Sebor; +Cc: Jeff Law, gcc-patches

On Fri, Nov 15, 2019 at 10:28 PM Martin Sebor <msebor@gmail.com> wrote:
>
> Thanks for the suggestion.  I will do that for GCC 11.  I take
> Richard's point that the attributes' semantics need to be clearly
> and carefully specified before they're put to use for optimization.

Before they are exposed to users please.  It doesn't help if we
specify the same attribute for optimization later when uses are out
in the wild "guessing" at what the possible interpretation is.

Maybe we can name your attributes maybe_readonly and friends
to clearly indicate that this is only a guess by the user so at most
usable for diagnostics but never for optimization.

Since we have quite costly attribute lookup I also prefer something
that translates to less attributes - how about
__attribute__((diag_argspec(1, readonly), diag_argspec(2, writeonly)))
to indicate argument 1 is maybe readonly, 2 is writeonly?  We can
then merge this into a single diag_arspec attribute instance we can
lookup.

> >
> > I don't see anything terribly concerning.  Looking forward to the final
> > iteration here.
>
> Attached is a subset of the original patch that just adds the three
> attributes and uses them to do buffer overflow checking.  I have
> also enhanced the detection of invalid arguments (null pointers,
> negative sizes).
>
> Retested on x86_64-linux.
>
> Martin

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

* Re: [WIP PATCH] add object access attributes (PR 83859)
  2019-11-18  9:00     ` Richard Biener
@ 2019-11-18 16:46       ` Martin Sebor
  2019-11-19  8:57         ` Richard Biener
  0 siblings, 1 reply; 19+ messages in thread
From: Martin Sebor @ 2019-11-18 16:46 UTC (permalink / raw)
  To: Richard Biener; +Cc: Jeff Law, gcc-patches

On 11/18/19 1:36 AM, Richard Biener wrote:
> On Fri, Nov 15, 2019 at 10:28 PM Martin Sebor <msebor@gmail.com> wrote:
>>
>> Thanks for the suggestion.  I will do that for GCC 11.  I take
>> Richard's point that the attributes' semantics need to be clearly
>> and carefully specified before they're put to use for optimization.
> 
> Before they are exposed to users please.  It doesn't help if we
> specify the same attribute for optimization later when uses are out
> in the wild "guessing" at what the possible interpretation is.
> 
> Maybe we can name your attributes maybe_readonly and friends
> to clearly indicate that this is only a guess by the user so at most
> usable for diagnostics but never for optimization.
> 
> Since we have quite costly attribute lookup I also prefer something
> that translates to less attributes - how about
> __attribute__((diag_argspec(1, readonly), diag_argspec(2, writeonly)))
> to indicate argument 1 is maybe readonly, 2 is writeonly?  We can
> then merge this into a single diag_arspec attribute instance we can
> lookup.

I can look into making a change along these lines.

I'm not fond of the idea of introducing a "maybe" kind of attributes
now and another parallel "for-sure" set later.  My goal is to have
the attributes express the same access constraints as those on
the arguments to built-in string functions like memcpy or strcat
(i.e., read_only only reads a pointed-to object, write_only only
writes, and, for strcat, read_write both reads and writes it).

Those properties are sufficiently well understood.  The three
attributes aren't intended to express constraints on aliasing
or on the side-effects of the functions, like restrict or
the const and pure attributes.

To let users do more than that, some additional annotation will
probably be necessary.  In my WIP patch I have a no_side_effect
attribute that lets functions do more than const and pure but
that's still work in progress that I don't plan to submit for
GCC 10.

Martin

> 
>>>
>>> I don't see anything terribly concerning.  Looking forward to the final
>>> iteration here.
>>
>> Attached is a subset of the original patch that just adds the three
>> attributes and uses them to do buffer overflow checking.  I have
>> also enhanced the detection of invalid arguments (null pointers,
>> negative sizes).
>>
>> Retested on x86_64-linux.
>>
>> Martin

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

* Re: [WIP PATCH] add object access attributes (PR 83859)
  2019-11-18 16:46       ` Martin Sebor
@ 2019-11-19  8:57         ` Richard Biener
  2019-11-21 17:12           ` [PATCH v3] " Martin Sebor
  0 siblings, 1 reply; 19+ messages in thread
From: Richard Biener @ 2019-11-19  8:57 UTC (permalink / raw)
  To: Martin Sebor; +Cc: Jeff Law, gcc-patches

On Mon, Nov 18, 2019 at 5:45 PM Martin Sebor <msebor@gmail.com> wrote:
>
> On 11/18/19 1:36 AM, Richard Biener wrote:
> > On Fri, Nov 15, 2019 at 10:28 PM Martin Sebor <msebor@gmail.com> wrote:
> >>
> >> Thanks for the suggestion.  I will do that for GCC 11.  I take
> >> Richard's point that the attributes' semantics need to be clearly
> >> and carefully specified before they're put to use for optimization.
> >
> > Before they are exposed to users please.  It doesn't help if we
> > specify the same attribute for optimization later when uses are out
> > in the wild "guessing" at what the possible interpretation is.
> >
> > Maybe we can name your attributes maybe_readonly and friends
> > to clearly indicate that this is only a guess by the user so at most
> > usable for diagnostics but never for optimization.
> >
> > Since we have quite costly attribute lookup I also prefer something
> > that translates to less attributes - how about
> > __attribute__((diag_argspec(1, readonly), diag_argspec(2, writeonly)))
> > to indicate argument 1 is maybe readonly, 2 is writeonly?  We can
> > then merge this into a single diag_arspec attribute instance we can
> > lookup.
>
> I can look into making a change along these lines.
>
> I'm not fond of the idea of introducing a "maybe" kind of attributes
> now and another parallel "for-sure" set later.  My goal is to have
> the attributes express the same access constraints as those on
> the arguments to built-in string functions like memcpy or strcat
> (i.e., read_only only reads a pointed-to object, write_only only
> writes, and, for strcat, read_write both reads and writes it).
>
> Those properties are sufficiently well understood.  The three
> attributes aren't intended to express constraints on aliasing
> or on the side-effects of the functions, like restrict or
> the const and pure attributes.

They at least sound like they do.  I didn't look at the latest version
of the patch but please amend the documentation of the attributes
to then say that they will never be used in a way affecting code
generation.

Thanks,
Richard.

>
> To let users do more than that, some additional annotation will
> probably be necessary.  In my WIP patch I have a no_side_effect
> attribute that lets functions do more than const and pure but
> that's still work in progress that I don't plan to submit for
> GCC 10.
>
> Martin
>
> >
> >>>
> >>> I don't see anything terribly concerning.  Looking forward to the final
> >>> iteration here.
> >>
> >> Attached is a subset of the original patch that just adds the three
> >> attributes and uses them to do buffer overflow checking.  I have
> >> also enhanced the detection of invalid arguments (null pointers,
> >> negative sizes).
> >>
> >> Retested on x86_64-linux.
> >>
> >> Martin
>

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

* [PATCH v3] add object access attributes (PR 83859)
  2019-11-19  8:57         ` Richard Biener
@ 2019-11-21 17:12           ` Martin Sebor
  2019-11-21 22:40             ` Jeff Law
  0 siblings, 1 reply; 19+ messages in thread
From: Martin Sebor @ 2019-11-21 17:12 UTC (permalink / raw)
  To: Richard Biener, Jeff Law; +Cc: gcc-patches

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

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

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

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*);

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

* Re: [PATCH v3] add object access attributes (PR 83859)
  2019-11-21 17:12           ` [PATCH v3] " Martin Sebor
@ 2019-11-21 22:40             ` Jeff Law
  2019-11-22  1:12               ` Martin Sebor
  0 siblings, 1 reply; 19+ messages in thread
From: Jeff Law @ 2019-11-21 22:40 UTC (permalink / raw)
  To: Martin Sebor, Richard Biener; +Cc: gcc-patches

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

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

* Re: [PATCH v3] add object access attributes (PR 83859)
  2019-11-21 22:40             ` Jeff Law
@ 2019-11-22  1:12               ` Martin Sebor
  2019-11-23  1:10                 ` [PATCH] Fix attribute access issues Jakub Jelinek
  0 siblings, 1 reply; 19+ messages in thread
From: Martin Sebor @ 2019-11-22  1:12 UTC (permalink / raw)
  To: Jeff Law, Richard Biener; +Cc: gcc-patches

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
> 

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

* [PATCH] Fix attribute access issues
  2019-11-22  1:12               ` Martin Sebor
@ 2019-11-23  1:10                 ` Jakub Jelinek
  2019-11-23 10:04                   ` Richard Biener
  2019-11-25  2:24                   ` Martin Sebor
  0 siblings, 2 replies; 19+ messages in thread
From: Jakub Jelinek @ 2019-11-23  1:10 UTC (permalink / raw)
  To: Jeff Law, Richard Biener, Martin Sebor; +Cc: gcc-patches

Hi!

On Thu, Nov 21, 2019 at 06:09:34PM -0700, Martin Sebor wrote:
> > > 	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.

I'm getting
+FAIL: gcc.dg/Wstringop-overflow-24.c (internal compiler error)
+FAIL: gcc.dg/Wstringop-overflow-24.c (test for excess errors)
on i686-linux, while it succeeds on x86_64-linux.  On a closer look,
there is a buffer overflow even on x86_64-linux as can be seen under
valgrind, plus memory leak.

The buffer overflow is in append_access_attrs:
==9759== Command: ./cc1 -quiet -Wall Wstringop-overflow-24.c
==9759== 
==9759== Invalid write of size 1
==9759==    at 0x483BD9F: strcpy (vg_replace_strmem.c:513)
==9759==    by 0xA11FF4: append_access_attrs(tree_node*, tree_node*, char const*, char, long*) (c-attribs.c:3934)
==9759==    by 0xA12AD3: handle_access_attribute(tree_node**, tree_node*, tree_node*, int, bool*) (c-attribs.c:4158)
==9759==    by 0x88E1BF: decl_attributes(tree_node**, tree_node*, int, tree_node*) (attribs.c:728)
==9759==    by 0x8A6A9B: c_decl_attributes(tree_node**, tree_node*, int) (c-decl.c:4944)
==9759==    by 0x8A6FE2: start_decl(c_declarator*, c_declspecs*, bool, tree_node*) (c-decl.c:5083)
==9759==    by 0x91CB15: c_parser_declaration_or_fndef(c_parser*, bool, bool, bool, bool, bool, tree_node**, vec<c_token, va_heap, vl_ptr>, bool, tree_node*, oacc_routine_data*, bool*) (c-parser.c:2216)
==9759==    by 0x91B742: c_parser_external_declaration(c_parser*) (c-parser.c:1690)
==9759==    by 0x91B25E: c_parser_translation_unit(c_parser*) (c-parser.c:1563)
==9759==    by 0x9590A4: c_parse_file() (c-parser.c:21524)
==9759==    by 0x9E308E: c_common_parse_file() (c-opts.c:1185)
==9759==    by 0x1211AEE: compile_file() (toplev.c:458)
==9759==  Address 0x5113f68 is 0 bytes after a block of size 8 alloc'd
==9759==    at 0x483880B: malloc (vg_replace_malloc.c:309)
==9759==    by 0x229BF17: xmalloc (xmalloc.c:147)
==9759==    by 0xA11FC0: append_access_attrs(tree_node*, tree_node*, char const*, char, long*) (c-attribs.c:3932)
==9759==    by 0xA12AD3: handle_access_attribute(tree_node**, tree_node*, tree_node*, int, bool*) (c-attribs.c:4158)
==9759==    by 0x88E1BF: decl_attributes(tree_node**, tree_node*, int, tree_node*) (attribs.c:728)
==9759==    by 0x8A6A9B: c_decl_attributes(tree_node**, tree_node*, int) (c-decl.c:4944)
==9759==    by 0x8A6FE2: start_decl(c_declarator*, c_declspecs*, bool, tree_node*) (c-decl.c:5083)
==9759==    by 0x91CB15: c_parser_declaration_or_fndef(c_parser*, bool, bool, bool, bool, bool, tree_node**, vec<c_token, va_heap, vl_ptr>, bool, tree_node*, oacc_routine_data*, bool*) (c-parser.c:2216)
==9759==    by 0x91B742: c_parser_external_declaration(c_parser*) (c-parser.c:1690)
==9759==    by 0x91B25E: c_parser_translation_unit(c_parser*) (c-parser.c:1563)
==9759==    by 0x9590A4: c_parse_file() (c-parser.c:21524)
==9759==    by 0x9E308E: c_common_parse_file() (c-opts.c:1185)
If n2 != 0, newlen is computed as n1 + n2, but that doesn't take into
account for the , that is added in between the two.

The following patch ought to fix both the buffer overflow (by adding 1 if n2
is non-zero), memory leak (freeing newspec buffer after creating the string;
I've considered using XALLOCAVEC instead, but I believe the string can be
arbitrarily long on functions with thousands of arguments), using XNEWVEC
instead of (type *) xmalloc, using auto_diagnostic_group to bind warning +
inform together and fixes a typo in the documentation.

Ok for trunk if it passes bootstrap/regtest on x86_64-linux and i686-linux?

2019-11-23  Jakub Jelinek  <jakub@redhat.com>

	PR middle-end/83859
	* doc/extend.texi (attribute access): Fix a typo.

	* c-attribs.c (append_access_attrs): Avoid buffer overflow.  Avoid
	memory leak.  Use XNEWVEC macro.  Use auto_diagnostic_group to
	group warning with inform together.
	(handle_access_attribute): Formatting fix.

--- gcc/doc/extend.texi.jj	2019-11-22 19:11:53.634970558 +0100
+++ gcc/doc/extend.texi	2019-11-23 01:34:33.344849287 +0100
@@ -2490,7 +2490,7 @@ The following attributes are supported o
 
 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
+as write-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.
 
--- gcc/c-family/c-attribs.c.jj	2019-11-22 19:11:54.000000000 +0100
+++ gcc/c-family/c-attribs.c	2019-11-23 01:44:50.306617000 +0100
@@ -3840,7 +3840,7 @@ append_access_attrs (tree t, tree attrs,
   if (idxs[1])
     n2 = sprintf (attrspec + n1 + 1, "%u", (unsigned) idxs[1] - 1);
 
-  size_t newlen = n1 + n2;
+  size_t newlen = n1 + n2 + !!n2;
   char *newspec = attrspec;
 
   if (tree acs = lookup_attribute ("access", attrs))
@@ -3869,6 +3869,7 @@ append_access_attrs (tree t, tree attrs,
 	  if (*attrspec != pos[-1])
 	    {
 	      /* Mismatch in access mode.  */
+	      auto_diagnostic_group d;
 	      if (warning (OPT_Wattributes,
 			   "attribute %qs mismatch with mode %qs",
 			   attrstr,
@@ -3884,6 +3885,7 @@ append_access_attrs (tree t, tree attrs,
 	  if ((n2 && pos[n1 - 1] != ','))
 	    {
 	      /* 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",
@@ -3897,6 +3899,7 @@ append_access_attrs (tree t, tree attrs,
 	  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",
@@ -3910,6 +3913,7 @@ append_access_attrs (tree t, tree attrs,
 	  if (n2 && strncmp (attrstr + n1 + 1, pos + n1, n2))
 	    {
 	      /* Mismatch in the value of the size argument.  */
+	      auto_diagnostic_group d;
 	      if (warning (OPT_Wattributes,
 			   "attribute %qs mismatch positional argument "
 			   "values %i and %i",
@@ -3929,7 +3933,7 @@ append_access_attrs (tree t, tree attrs,
 	attrspec[n1] = ',';
 
       size_t len = strlen (str);
-      newspec = (char *) xmalloc (newlen + len + 1);
+      newspec = XNEWVEC (char, newlen + len + 1);
       strcpy (newspec, str);
       strcpy (newspec + len, attrspec);
       newlen += len;
@@ -3938,7 +3942,10 @@ append_access_attrs (tree t, tree attrs,
     /* Connect the two substrings formatted above into a single one.  */
     attrspec[n1] = ',';
 
-  return build_string (newlen + 1, newspec);
+  tree ret = build_string (newlen + 1, newspec);
+  if (newspec != attrspec)
+    XDELETEVEC (newspec);
+  return ret;
 }
 
 /* Handle the access attribute (read_only, write_only, and read_write).  */
@@ -4168,7 +4175,8 @@ handle_access_attribute (tree *node, tre
     {
       /* 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);
+      tree new_attrs
+	= append_access_attrs (node[1], attrs, attrstr, code, idxs);
       if (!new_attrs)
 	return NULL_TREE;
 


	Jakub

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

* Re: [PATCH] Fix attribute access issues
  2019-11-23  1:10                 ` [PATCH] Fix attribute access issues Jakub Jelinek
@ 2019-11-23 10:04                   ` Richard Biener
  2019-11-25  2:24                   ` Martin Sebor
  1 sibling, 0 replies; 19+ messages in thread
From: Richard Biener @ 2019-11-23 10:04 UTC (permalink / raw)
  To: Jakub Jelinek, Jeff Law, Martin Sebor; +Cc: gcc-patches

On November 23, 2019 2:03:21 AM GMT+01:00, Jakub Jelinek <jakub@redhat.com> wrote:
>Hi!
>
>On Thu, Nov 21, 2019 at 06:09:34PM -0700, Martin Sebor wrote:
>> > > 	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.
>
>I'm getting
>+FAIL: gcc.dg/Wstringop-overflow-24.c (internal compiler error)
>+FAIL: gcc.dg/Wstringop-overflow-24.c (test for excess errors)
>on i686-linux, while it succeeds on x86_64-linux.  On a closer look,
>there is a buffer overflow even on x86_64-linux as can be seen under
>valgrind, plus memory leak.
>
>The buffer overflow is in append_access_attrs:
>==9759== Command: ./cc1 -quiet -Wall Wstringop-overflow-24.c
>==9759== 
>==9759== Invalid write of size 1
>==9759==    at 0x483BD9F: strcpy (vg_replace_strmem.c:513)
>==9759==    by 0xA11FF4: append_access_attrs(tree_node*, tree_node*,
>char const*, char, long*) (c-attribs.c:3934)
>==9759==    by 0xA12AD3: handle_access_attribute(tree_node**,
>tree_node*, tree_node*, int, bool*) (c-attribs.c:4158)
>==9759==    by 0x88E1BF: decl_attributes(tree_node**, tree_node*, int,
>tree_node*) (attribs.c:728)
>==9759==    by 0x8A6A9B: c_decl_attributes(tree_node**, tree_node*,
>int) (c-decl.c:4944)
>==9759==    by 0x8A6FE2: start_decl(c_declarator*, c_declspecs*, bool,
>tree_node*) (c-decl.c:5083)
>==9759==    by 0x91CB15: c_parser_declaration_or_fndef(c_parser*, bool,
>bool, bool, bool, bool, tree_node**, vec<c_token, va_heap, vl_ptr>,
>bool, tree_node*, oacc_routine_data*, bool*) (c-parser.c:2216)
>==9759==    by 0x91B742: c_parser_external_declaration(c_parser*)
>(c-parser.c:1690)
>==9759==    by 0x91B25E: c_parser_translation_unit(c_parser*)
>(c-parser.c:1563)
>==9759==    by 0x9590A4: c_parse_file() (c-parser.c:21524)
>==9759==    by 0x9E308E: c_common_parse_file() (c-opts.c:1185)
>==9759==    by 0x1211AEE: compile_file() (toplev.c:458)
>==9759==  Address 0x5113f68 is 0 bytes after a block of size 8 alloc'd
>==9759==    at 0x483880B: malloc (vg_replace_malloc.c:309)
>==9759==    by 0x229BF17: xmalloc (xmalloc.c:147)
>==9759==    by 0xA11FC0: append_access_attrs(tree_node*, tree_node*,
>char const*, char, long*) (c-attribs.c:3932)
>==9759==    by 0xA12AD3: handle_access_attribute(tree_node**,
>tree_node*, tree_node*, int, bool*) (c-attribs.c:4158)
>==9759==    by 0x88E1BF: decl_attributes(tree_node**, tree_node*, int,
>tree_node*) (attribs.c:728)
>==9759==    by 0x8A6A9B: c_decl_attributes(tree_node**, tree_node*,
>int) (c-decl.c:4944)
>==9759==    by 0x8A6FE2: start_decl(c_declarator*, c_declspecs*, bool,
>tree_node*) (c-decl.c:5083)
>==9759==    by 0x91CB15: c_parser_declaration_or_fndef(c_parser*, bool,
>bool, bool, bool, bool, tree_node**, vec<c_token, va_heap, vl_ptr>,
>bool, tree_node*, oacc_routine_data*, bool*) (c-parser.c:2216)
>==9759==    by 0x91B742: c_parser_external_declaration(c_parser*)
>(c-parser.c:1690)
>==9759==    by 0x91B25E: c_parser_translation_unit(c_parser*)
>(c-parser.c:1563)
>==9759==    by 0x9590A4: c_parse_file() (c-parser.c:21524)
>==9759==    by 0x9E308E: c_common_parse_file() (c-opts.c:1185)
>If n2 != 0, newlen is computed as n1 + n2, but that doesn't take into
>account for the , that is added in between the two.
>
>The following patch ought to fix both the buffer overflow (by adding 1
>if n2
>is non-zero), memory leak (freeing newspec buffer after creating the
>string;
>I've considered using XALLOCAVEC instead, but I believe the string can
>be
>arbitrarily long on functions with thousands of arguments), using
>XNEWVEC
>instead of (type *) xmalloc, using auto_diagnostic_group to bind
>warning +
>inform together and fixes a typo in the documentation.
>
>Ok for trunk if it passes bootstrap/regtest on x86_64-linux and
>i686-linux?

Ok. 

Richard. 

>2019-11-23  Jakub Jelinek  <jakub@redhat.com>
>
>	PR middle-end/83859
>	* doc/extend.texi (attribute access): Fix a typo.
>
>	* c-attribs.c (append_access_attrs): Avoid buffer overflow.  Avoid
>	memory leak.  Use XNEWVEC macro.  Use auto_diagnostic_group to
>	group warning with inform together.
>	(handle_access_attribute): Formatting fix.
>
>--- gcc/doc/extend.texi.jj	2019-11-22 19:11:53.634970558 +0100
>+++ gcc/doc/extend.texi	2019-11-23 01:34:33.344849287 +0100
>@@ -2490,7 +2490,7 @@ The following attributes are supported o
> 
> 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
>+as write-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.
> 
>--- gcc/c-family/c-attribs.c.jj	2019-11-22 19:11:54.000000000 +0100
>+++ gcc/c-family/c-attribs.c	2019-11-23 01:44:50.306617000 +0100
>@@ -3840,7 +3840,7 @@ append_access_attrs (tree t, tree attrs,
>   if (idxs[1])
>     n2 = sprintf (attrspec + n1 + 1, "%u", (unsigned) idxs[1] - 1);
> 
>-  size_t newlen = n1 + n2;
>+  size_t newlen = n1 + n2 + !!n2;
>   char *newspec = attrspec;
> 
>   if (tree acs = lookup_attribute ("access", attrs))
>@@ -3869,6 +3869,7 @@ append_access_attrs (tree t, tree attrs,
> 	  if (*attrspec != pos[-1])
> 	    {
> 	      /* Mismatch in access mode.  */
>+	      auto_diagnostic_group d;
> 	      if (warning (OPT_Wattributes,
> 			   "attribute %qs mismatch with mode %qs",
> 			   attrstr,
>@@ -3884,6 +3885,7 @@ append_access_attrs (tree t, tree attrs,
> 	  if ((n2 && pos[n1 - 1] != ','))
> 	    {
> 	      /* 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",
>@@ -3897,6 +3899,7 @@ append_access_attrs (tree t, tree attrs,
> 	  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",
>@@ -3910,6 +3913,7 @@ append_access_attrs (tree t, tree attrs,
> 	  if (n2 && strncmp (attrstr + n1 + 1, pos + n1, n2))
> 	    {
> 	      /* Mismatch in the value of the size argument.  */
>+	      auto_diagnostic_group d;
> 	      if (warning (OPT_Wattributes,
> 			   "attribute %qs mismatch positional argument "
> 			   "values %i and %i",
>@@ -3929,7 +3933,7 @@ append_access_attrs (tree t, tree attrs,
> 	attrspec[n1] = ',';
> 
>       size_t len = strlen (str);
>-      newspec = (char *) xmalloc (newlen + len + 1);
>+      newspec = XNEWVEC (char, newlen + len + 1);
>       strcpy (newspec, str);
>       strcpy (newspec + len, attrspec);
>       newlen += len;
>@@ -3938,7 +3942,10 @@ append_access_attrs (tree t, tree attrs,
>   /* Connect the two substrings formatted above into a single one.  */
>     attrspec[n1] = ',';
> 
>-  return build_string (newlen + 1, newspec);
>+  tree ret = build_string (newlen + 1, newspec);
>+  if (newspec != attrspec)
>+    XDELETEVEC (newspec);
>+  return ret;
> }
> 
>/* Handle the access attribute (read_only, write_only, and read_write).
> */
>@@ -4168,7 +4175,8 @@ handle_access_attribute (tree *node, tre
>     {
>       /* 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);
>+      tree new_attrs
>+	= append_access_attrs (node[1], attrs, attrstr, code, idxs);
>       if (!new_attrs)
> 	return NULL_TREE;
> 
>
>
>	Jakub

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

* Re: [PATCH] Fix attribute access issues
  2019-11-23  1:10                 ` [PATCH] Fix attribute access issues Jakub Jelinek
  2019-11-23 10:04                   ` Richard Biener
@ 2019-11-25  2:24                   ` Martin Sebor
  1 sibling, 0 replies; 19+ messages in thread
From: Martin Sebor @ 2019-11-25  2:24 UTC (permalink / raw)
  To: Jakub Jelinek, Jeff Law, Richard Biener; +Cc: gcc-patches

On 11/22/19 6:03 PM, Jakub Jelinek wrote:
> Hi!
> 
> On Thu, Nov 21, 2019 at 06:09:34PM -0700, Martin Sebor wrote:
>>>> 	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.
> 
> I'm getting
> +FAIL: gcc.dg/Wstringop-overflow-24.c (internal compiler error)
> +FAIL: gcc.dg/Wstringop-overflow-24.c (test for excess errors)
> on i686-linux, while it succeeds on x86_64-linux.  On a closer look,
> there is a buffer overflow even on x86_64-linux as can be seen under
> valgrind, plus memory leak.
> 
> The buffer overflow is in append_access_attrs:
> ==9759== Command: ./cc1 -quiet -Wall Wstringop-overflow-24.c
> ==9759==
> ==9759== Invalid write of size 1
> ==9759==    at 0x483BD9F: strcpy (vg_replace_strmem.c:513)
> ==9759==    by 0xA11FF4: append_access_attrs(tree_node*, tree_node*, char const*, char, long*) (c-attribs.c:3934)
> ==9759==    by 0xA12AD3: handle_access_attribute(tree_node**, tree_node*, tree_node*, int, bool*) (c-attribs.c:4158)
> ==9759==    by 0x88E1BF: decl_attributes(tree_node**, tree_node*, int, tree_node*) (attribs.c:728)
> ==9759==    by 0x8A6A9B: c_decl_attributes(tree_node**, tree_node*, int) (c-decl.c:4944)
> ==9759==    by 0x8A6FE2: start_decl(c_declarator*, c_declspecs*, bool, tree_node*) (c-decl.c:5083)
> ==9759==    by 0x91CB15: c_parser_declaration_or_fndef(c_parser*, bool, bool, bool, bool, bool, tree_node**, vec<c_token, va_heap, vl_ptr>, bool, tree_node*, oacc_routine_data*, bool*) (c-parser.c:2216)
> ==9759==    by 0x91B742: c_parser_external_declaration(c_parser*) (c-parser.c:1690)
> ==9759==    by 0x91B25E: c_parser_translation_unit(c_parser*) (c-parser.c:1563)
> ==9759==    by 0x9590A4: c_parse_file() (c-parser.c:21524)
> ==9759==    by 0x9E308E: c_common_parse_file() (c-opts.c:1185)
> ==9759==    by 0x1211AEE: compile_file() (toplev.c:458)
> ==9759==  Address 0x5113f68 is 0 bytes after a block of size 8 alloc'd
> ==9759==    at 0x483880B: malloc (vg_replace_malloc.c:309)
> ==9759==    by 0x229BF17: xmalloc (xmalloc.c:147)
> ==9759==    by 0xA11FC0: append_access_attrs(tree_node*, tree_node*, char const*, char, long*) (c-attribs.c:3932)
> ==9759==    by 0xA12AD3: handle_access_attribute(tree_node**, tree_node*, tree_node*, int, bool*) (c-attribs.c:4158)
> ==9759==    by 0x88E1BF: decl_attributes(tree_node**, tree_node*, int, tree_node*) (attribs.c:728)
> ==9759==    by 0x8A6A9B: c_decl_attributes(tree_node**, tree_node*, int) (c-decl.c:4944)
> ==9759==    by 0x8A6FE2: start_decl(c_declarator*, c_declspecs*, bool, tree_node*) (c-decl.c:5083)
> ==9759==    by 0x91CB15: c_parser_declaration_or_fndef(c_parser*, bool, bool, bool, bool, bool, tree_node**, vec<c_token, va_heap, vl_ptr>, bool, tree_node*, oacc_routine_data*, bool*) (c-parser.c:2216)
> ==9759==    by 0x91B742: c_parser_external_declaration(c_parser*) (c-parser.c:1690)
> ==9759==    by 0x91B25E: c_parser_translation_unit(c_parser*) (c-parser.c:1563)
> ==9759==    by 0x9590A4: c_parse_file() (c-parser.c:21524)
> ==9759==    by 0x9E308E: c_common_parse_file() (c-opts.c:1185)
> If n2 != 0, newlen is computed as n1 + n2, but that doesn't take into
> account for the , that is added in between the two.
> 
> The following patch ought to fix both the buffer overflow (by adding 1 if n2
> is non-zero), memory leak (freeing newspec buffer after creating the string;
> I've considered using XALLOCAVEC instead, but I believe the string can be
> arbitrarily long on functions with thousands of arguments), using XNEWVEC
> instead of (type *) xmalloc, using auto_diagnostic_group to bind warning +
> inform together and fixes a typo in the documentation.
> 
> Ok for trunk if it passes bootstrap/regtest on x86_64-linux and i686-linux?

Thanks for the fix.

The buffer overflow enhancement I posted a couple of weeks ago has
logic to detect very simple forms of this bug:
https://gcc.gnu.org/ml/gcc-patches/2019-11/msg00812.html
It knows how to figure out that the strcpy call below overflows:

   void* f1 (const char *s1)
   {
     __SIZE_TYPE__ n1 = __builtin_strlen (s1);
     char *s2 = __builtin_malloc (n1);
     __builtin_strcpy (s2, s1);
     return s2;
   }

   warning: ‘__builtin_strcpy’ writing one too many bytes into a region 
of a size that depends on ‘strlen’ [-Wstringop-overflow=]
       5 |   __builtin_strcpy (s2, s1);
         |   ^~~~~~~~~~~~~~~~~~~~~~~~~

Unfortunately, it doesn't yet know how to see the similar problem
in more complicated code such as this:

   void* f2 (const char *s1, const char *s2)
   {
     __SIZE_TYPE__ n1 = __builtin_strlen (s1);
     __SIZE_TYPE__ n2 = __builtin_strlen (s2);
     char *s3 = __builtin_malloc (n1 + n2);
     __builtin_strcpy (s3, s1);
     __builtin_strcat (s3, s2);
     return s3;
   }

Detecting that will take quite a bit more work.  Some sort of
a symbolic constraint evaluation engine.

Martin

> 
> 2019-11-23  Jakub Jelinek  <jakub@redhat.com>
> 
> 	PR middle-end/83859
> 	* doc/extend.texi (attribute access): Fix a typo.
> 
> 	* c-attribs.c (append_access_attrs): Avoid buffer overflow.  Avoid
> 	memory leak.  Use XNEWVEC macro.  Use auto_diagnostic_group to
> 	group warning with inform together.
> 	(handle_access_attribute): Formatting fix.
> 
> --- gcc/doc/extend.texi.jj	2019-11-22 19:11:53.634970558 +0100
> +++ gcc/doc/extend.texi	2019-11-23 01:34:33.344849287 +0100
> @@ -2490,7 +2490,7 @@ The following attributes are supported o
>   
>   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
> +as write-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.
>   
> --- gcc/c-family/c-attribs.c.jj	2019-11-22 19:11:54.000000000 +0100
> +++ gcc/c-family/c-attribs.c	2019-11-23 01:44:50.306617000 +0100
> @@ -3840,7 +3840,7 @@ append_access_attrs (tree t, tree attrs,
>     if (idxs[1])
>       n2 = sprintf (attrspec + n1 + 1, "%u", (unsigned) idxs[1] - 1);
>   
> -  size_t newlen = n1 + n2;
> +  size_t newlen = n1 + n2 + !!n2;
>     char *newspec = attrspec;
>   
>     if (tree acs = lookup_attribute ("access", attrs))
> @@ -3869,6 +3869,7 @@ append_access_attrs (tree t, tree attrs,
>   	  if (*attrspec != pos[-1])
>   	    {
>   	      /* Mismatch in access mode.  */
> +	      auto_diagnostic_group d;
>   	      if (warning (OPT_Wattributes,
>   			   "attribute %qs mismatch with mode %qs",
>   			   attrstr,
> @@ -3884,6 +3885,7 @@ append_access_attrs (tree t, tree attrs,
>   	  if ((n2 && pos[n1 - 1] != ','))
>   	    {
>   	      /* 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",
> @@ -3897,6 +3899,7 @@ append_access_attrs (tree t, tree attrs,
>   	  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",
> @@ -3910,6 +3913,7 @@ append_access_attrs (tree t, tree attrs,
>   	  if (n2 && strncmp (attrstr + n1 + 1, pos + n1, n2))
>   	    {
>   	      /* Mismatch in the value of the size argument.  */
> +	      auto_diagnostic_group d;
>   	      if (warning (OPT_Wattributes,
>   			   "attribute %qs mismatch positional argument "
>   			   "values %i and %i",
> @@ -3929,7 +3933,7 @@ append_access_attrs (tree t, tree attrs,
>   	attrspec[n1] = ',';
>   
>         size_t len = strlen (str);
> -      newspec = (char *) xmalloc (newlen + len + 1);
> +      newspec = XNEWVEC (char, newlen + len + 1);
>         strcpy (newspec, str);
>         strcpy (newspec + len, attrspec);
>         newlen += len;
> @@ -3938,7 +3942,10 @@ append_access_attrs (tree t, tree attrs,
>       /* Connect the two substrings formatted above into a single one.  */
>       attrspec[n1] = ',';
>   
> -  return build_string (newlen + 1, newspec);
> +  tree ret = build_string (newlen + 1, newspec);
> +  if (newspec != attrspec)
> +    XDELETEVEC (newspec);
> +  return ret;
>   }
>   
>   /* Handle the access attribute (read_only, write_only, and read_write).  */
> @@ -4168,7 +4175,8 @@ handle_access_attribute (tree *node, tre
>       {
>         /* 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);
> +      tree new_attrs
> +	= append_access_attrs (node[1], attrs, attrstr, code, idxs);
>         if (!new_attrs)
>   	return NULL_TREE;
>   
> 
> 
> 	Jakub
> 

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

end of thread, other threads:[~2019-11-24 23:11 UTC | newest]

Thread overview: 19+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-09-29 19:52 [WIP PATCH] add object access attributes (PR 83859) Martin Sebor
2019-09-30  7:37 ` Richard Biener
2019-09-30 15:41   ` Martin Sebor
2019-09-30 21:34 ` Joseph Myers
2019-10-01  2:36   ` Martin Sebor
2019-10-17 16:44 ` [PING] " Martin Sebor
2019-10-24 14:42   ` [PING 2] " Martin Sebor
2019-10-27 17:37 ` Jeff Law
2019-10-28 10:18   ` Richard Biener
2019-11-15 21:41   ` Martin Sebor
2019-11-18  9:00     ` Richard Biener
2019-11-18 16:46       ` Martin Sebor
2019-11-19  8:57         ` Richard Biener
2019-11-21 17:12           ` [PATCH v3] " Martin Sebor
2019-11-21 22:40             ` Jeff Law
2019-11-22  1:12               ` Martin Sebor
2019-11-23  1:10                 ` [PATCH] Fix attribute access issues Jakub Jelinek
2019-11-23 10:04                   ` Richard Biener
2019-11-25  2:24                   ` Martin Sebor

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