diff options
author | Martin Sebor <msebor@redhat.com> | 2019-11-22 17:14:17 +0000 |
---|---|---|
committer | Martin Sebor <msebor@gcc.gnu.org> | 2019-11-22 10:14:17 -0700 |
commit | 54aa6b58fe2fe73bbe67e0485777e0c410a18673 (patch) | |
tree | 11281ae8b5d5a5f2a57ffc60d7d8cfe951a953d8 /gcc/c-family | |
parent | b5338fb359ea3480d6ed37bbc52fe2df49b82fb9 (diff) | |
download | gcc-54aa6b58fe2fe73bbe67e0485777e0c410a18673.zip gcc-54aa6b58fe2fe73bbe67e0485777e0c410a18673.tar.gz gcc-54aa6b58fe2fe73bbe67e0485777e0c410a18673.tar.bz2 |
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
Diffstat (limited to 'gcc/c-family')
-rw-r--r-- | gcc/c-family/ChangeLog | 12 | ||||
-rw-r--r-- | gcc/c-family/c-attribs.c | 388 | ||||
-rw-r--r-- | gcc/c-family/c-common.c | 8 | ||||
-rw-r--r-- | gcc/c-family/c-common.h | 2 |
4 files changed, 404 insertions, 6 deletions
diff --git a/gcc/c-family/ChangeLog b/gcc/c-family/ChangeLog index b1dc0bb..9c93f7f 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 b727f66..e307160 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 f779acc..3e70b6d 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 f3478d3..bed4d0c 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) |