summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Sebor <msebor@redhat.com>2019-11-22 17:14:17 +0000
committerMartin Sebor <msebor@gcc.gnu.org>2019-11-22 10:14:17 -0700
commit54aa6b58fe2fe73bbe67e0485777e0c410a18673 (patch)
tree11281ae8b5d5a5f2a57ffc60d7d8cfe951a953d8
parentb5338fb359ea3480d6ed37bbc52fe2df49b82fb9 (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. (get_size_range): Avoid null argument. * 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-23.c: New test. * gcc.dg/Wstringop-overflow-24.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. From-SVN: r278624
-rw-r--r--gcc/ChangeLog18
-rw-r--r--gcc/attribs.c22
-rw-r--r--gcc/attribs.h20
-rw-r--r--gcc/builtins.c215
-rw-r--r--gcc/builtins.h2
-rw-r--r--gcc/c-family/ChangeLog12
-rw-r--r--gcc/c-family/c-attribs.c388
-rw-r--r--gcc/c-family/c-common.c8
-rw-r--r--gcc/c-family/c-common.h2
-rw-r--r--gcc/calls.c332
-rw-r--r--gcc/doc/extend.texi72
-rw-r--r--gcc/testsuite/ChangeLog11
-rw-r--r--gcc/testsuite/c-c++-common/attr-nonstring-8.c4
-rw-r--r--gcc/testsuite/gcc.dg/Wstringop-overflow-23.c176
-rw-r--r--gcc/testsuite/gcc.dg/Wstringop-overflow-24.c204
-rw-r--r--gcc/testsuite/gcc.dg/attr-access-read-only.c96
-rw-r--r--gcc/testsuite/gcc.dg/attr-access-read-write-2.c61
-rw-r--r--gcc/testsuite/gcc.dg/attr-access-read-write.c92
-rw-r--r--gcc/testsuite/gcc.dg/attr-access-write-only.c89
19 files changed, 1748 insertions, 76 deletions
diff --git a/gcc/ChangeLog b/gcc/ChangeLog
index f042116fa4d..0b3b47714ea 100644
--- a/gcc/ChangeLog
+++ b/gcc/ChangeLog
@@ -1,3 +1,21 @@
+2019-11-22 Martin Sebor <msebor@redhat.com>
+
+ 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.
+ (get_size_range): Avoid null argument.
+ * doc/extend.texi (attribute access): Document new attribute.
+
2019-11-22 Andrew Stubbs <ams@codesourcery.com>
* config/gcn/gcn.c (OMP_LDS_SIZE): Define.
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 f8cd4b44fa8..8296d846171 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -3340,7 +3340,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)
{
@@ -3436,16 +3436,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;
@@ -3474,37 +3484,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;
@@ -3527,21 +3569,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;
}
@@ -3551,18 +3608,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;
}
}
@@ -3577,26 +3650,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 d9e27ca16aa..aa83a46a684 100644
--- a/gcc/builtins.h
+++ b/gcc/builtins.h
@@ -151,5 +151,7 @@ bool check_nul_terminated_array (tree, tree, tree = NULL_TREE);
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/ChangeLog b/gcc/c-family/ChangeLog
index b1dc0bb1212..9c93f7f777a 100644
--- a/gcc/c-family/ChangeLog
+++ b/gcc/c-family/ChangeLog
@@ -1,3 +1,15 @@
+2019-11-22 Martin Sebor <msebor@redhat.com>
+
+ 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.
+
2019-11-21 Joseph Myers <joseph@codesourcery.com>
* c-attribs.c (handle_fallthrough_attribute): Use pedwarn instead
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index b727f6605fb..e307160a453 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -123,6 +123,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 *);
@@ -477,6 +479,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 }
};
@@ -508,7 +512,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);
@@ -3795,6 +3800,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 f779acc0387..3e70b6d72f6 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 f3478d39beb..bed4d0c8764 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 62a98e939c8..3a63d48f857 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/testsuite/ChangeLog b/gcc/testsuite/ChangeLog
index 96c0f899bce..b9d86958376 100644
--- a/gcc/testsuite/ChangeLog
+++ b/gcc/testsuite/ChangeLog
@@ -1,5 +1,16 @@
2019-11-22 Martin Sebor <msebor@redhat.com>
+ PR middle-end/83859
+ * c-c++-common/attr-nonstring-8.c: Adjust text of expected warning.
+ * gcc.dg/Wstringop-overflow-23.c: New test.
+ * gcc.dg/Wstringop-overflow-24.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.
+
+2019-11-22 Martin Sebor <msebor@redhat.com>
+
PR middle-end/88226
* gcc.dg/Wstringop-overflow-22.c: New test.
* gcc.dg/tree-ssa/builtin-fprintf-warn-1.c: Remove xfails.
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-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/Wstringop-overflow-24.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-24.c
new file mode 100644
index 00000000000..8a490d7b7ef
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-24.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/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*);