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