PR middle-end/83859 - attributes to associate pointer arguments and sizes
authorMartin Sebor <msebor@redhat.com>
Fri, 22 Nov 2019 17:14:17 +0000 (17:14 +0000)
committerMartin Sebor <msebor@gcc.gnu.org>
Fri, 22 Nov 2019 17:14:17 +0000 (10:14 -0700)
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

19 files changed:
gcc/ChangeLog
gcc/attribs.c
gcc/attribs.h
gcc/builtins.c
gcc/builtins.h
gcc/c-family/ChangeLog
gcc/c-family/c-attribs.c
gcc/c-family/c-common.c
gcc/c-family/c-common.h
gcc/calls.c
gcc/doc/extend.texi
gcc/testsuite/ChangeLog
gcc/testsuite/c-c++-common/attr-nonstring-8.c
gcc/testsuite/gcc.dg/Wstringop-overflow-23.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/Wstringop-overflow-24.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/attr-access-read-only.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/attr-access-read-write-2.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/attr-access-read-write.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/attr-access-write-only.c [new file with mode: 0644]

index f042116..0b3b477 100644 (file)
@@ -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.
index b89be58..de34918 100644 (file)
@@ -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));
 
index 23a7321..9bc1600 100644 (file)
@@ -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
index f8cd4b4..8296d84 100644 (file)
@@ -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;
     }
 
index d9e27ca..aa83a46 100644 (file)
@@ -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 */
index b1dc0bb..9c93f7f 100644 (file)
@@ -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
index b727f66..e307160 100644 (file)
@@ -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.  */
 
index f779acc..3e70b6d 100644 (file)
@@ -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))
index f3478d3..bed4d0c 100644 (file)
@@ -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)
index 6292135..15627ab 100644 (file)
@@ -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.
index 62a98e9..3a63d48 100644 (file)
@@ -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
index 96c0f89..b9d8695 100644 (file)
@@ -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.
index 36ab2a6..fbae8ba 100644 (file)
@@ -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 (file)
index 0000000..f709434
--- /dev/null
@@ -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 (file)
index 0000000..8a490d7
--- /dev/null
@@ -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 (file)
index 0000000..9acd769
--- /dev/null
@@ -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 (file)
index 0000000..c2ac6c3
--- /dev/null
@@ -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 (file)
index 0000000..c97e54b
--- /dev/null
@@ -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 (file)
index 0000000..008f5a3
--- /dev/null
@@ -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*);