PR middle-end/83859 - Please add new attribute which will establish relation between parameters for buffer and its size gcc/ChangeLog: PR middle-end/83859 * builtin-attrs.def (ATTR_READ_ONLY): New. *(ATTR_READ_WRITE, ATTR_WRITE_ONLY): New. (ATTR_NOTHROW_WRONLY1_LEAF, ATTR_NOTHROW_WRONLY1_2_LEAF): New. (ATTR_NOTHROW_WRONLY1_3_LEAF, ATTR_NOTHROW_WRONLY2_3_LEAF): New. (ATTR_RET1_NOTHROW_WRONLY1_LEAF, ATTR_RET1_NOTHROW_WRONLY1_3_LEAF): New. (ATTR_RET1_NOTHROW_NONNULL_RDONLY2_LEAF): New. (ATTR_RET1_NOTHROW_NONNULL_RDWR1_RDONLY2_LEAF): New. (ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_3_LEAF): New. (ATTR_RET1_NOTHROW_WRONLY1_RDONLY2_LEAF): New. (ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_LEAF): New. (ATTR_MALLOC_NOTHROW_NONNULL_RDONLY1_LEAF): New. (ATTR_PURE_NOTHROW_NONNULL_RDONLY1_LEAF): New. (ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF): New. (ATTR_RETNONNULL_RDONLY2_3_NOTHROW_LEAF): New. (ATTR_RETNONNULL_WRONLY1_3_RDONLY2_3_NOTHROW_LEAF): New. (ATTR_PURE_NOTHROW_NONNULL_RDONLY1_3_LEAF): New. (ATTR_PURE_NOTHROW_NONNULL_RDONLY1_2_3_LEAF): New. (ATTR_RETNONNULL_RDONLY2_NOTHROW_LEAF): New. (ATTR_RETNONNULL_WRONLY1_RDONLY2_NOTHROW_LEAF): New. (ATTR_RETNONNULL_WRONLY1_3_RDONLY2_NOTHROW_LEAF): New. * builtins.c (check_access): Make extern. Consistently set the no-warning bit after issuing a warning. * builtins.h (check_access): Declare. * builtins.def (bcopy, bzero, index, memchr, memcmp, memcpy): Add read_only and write_only attributes. (memset, rindex, stpcpy, stpncpy, strcasecmp, strcat): Same. (strchr, strcmp, strcpy, strcspn, strdup, strndup, strlen): Same. (strncasecmp, strncat, strncmp, strncpy, strrchr, strspn, strstr): Same. (free, __memcpy_chk, __memmove_chk, __memset_chk): Same. (__strcpy_chk, __strncpy_chk): Same. * calls.c (rdwr_access_hash): New type. (rdwr_map): Same. (init_attr_rdwr_indices): New function. (maybe_warn_rdwr_sizes): Same. (initialize_argument_information): Call init_attr_rdwr_indices. Call maybe_warn_rdwr_sizes. * doc/extend.texi (read_only): Document new attribute. (write_only, read_write): Same. gcc/c-family/ChangeLog: PR middle-end/83859 * c-attribs.c (handle_read_only_attribute): New function. (handle_write_only_attribute): Same. (handle_read_write_attribute): Same. (c_common_attribute_table): Add new attributes. (get_argument_type): New function. (handle_rdwr_attributes): Same. (has_attribute): Add argument to callback signature. Pass to it a new value. (get_nonnull_operand): Rename... (get_attribute_operand): ...to this. * c-common.c (get_nonnull_operand): Rename... (get_attribute_operand): ...to this. (build_binary_op): Add a new argument. (default_conversion): Same. (has_attribute): Adjust argument type. gcc/testsuite/ChangeLog: PR middle-end/83859 * c-c++-common/attr-nonstring-8.c: Adjust text of expected warning. * gcc.dg/Wstringop-overflow-22.c: New test. * gcc.dg/Wstringop-overflow-23.c: New test. * gcc.dg/attr-read-only.c: New test. * gcc.dg/attr-write-only.c: New test. diff --git a/gcc/attribs.c b/gcc/attribs.c index b89be5834de..6a1cb3270d2 100644 --- a/gcc/attribs.c +++ b/gcc/attribs.c @@ -33,6 +33,7 @@ along with GCC; see the file COPYING3. If not see #include "diagnostic.h" #include "pretty-print.h" #include "intl.h" +#include "gimple.h" /* Table of the tables of attributes (common, language, format, machine) searched. */ @@ -2021,6 +2022,135 @@ maybe_diag_alias_attributes (tree alias, tree target) } } +/* Given function type FNTYPE, initialize ACCESS based on the function's + first access attribute specifier, if one exists, or advance it to the + next specifier if it's already been initialized. Return true if + ACCESS has been initialized or advanced to a valid access specifier, + false otherwise. */ + +bool +get_attr_access_ptr_and_size (tree fntype, attr_access *access) +{ + if (!access->attrs) + access->attrs = TYPE_ATTRIBUTES (fntype); + else if (TREE_CODE (access->attrs) != TREE_LIST) + { + /* The last call sets ACCESS->ATTRS to something other than + TREE_LIST to indicate it's reached the end. */ + return false; + } + + if (!access->attrs) + return false; + + const char *attr_name; + switch (access->kind) + { + case attr_access::read_only: + attr_name = "read_only"; + break; + + case attr_access::read_write: + attr_name = "read_write"; + break; + + case attr_access::write_only: + attr_name = "write_only"; + break; + + default: + gcc_unreachable (); + } + + access->attrs = lookup_attribute (attr_name, access->attrs); + if (!access->attrs) + return false; + + /* When operands are specified, iterate over them. */ + tree opers = TREE_VALUE (access->attrs); + if (opers) + access->ptrarg = TREE_INT_CST_LOW (TREE_VALUE (opers)) - 1; + else + { + /* When no operands are specified, the attribute applies to every + pointer argument. Use ARGCHAIN to find the next such argument + if it exists. */ + if (access->argchain) + access->argchain = TREE_CHAIN (access->argchain); + else + { + access->ptrarg = 0; + access->argchain = TYPE_ARG_TYPES (fntype); + } + + while (!POINTER_TYPE_P (TREE_VALUE (access->argchain))) + { + access->argchain = TREE_CHAIN (access->argchain); + if (!access->argchain) + return false; + ++access->ptrarg; + } + } + + if (opers) + opers = TREE_CHAIN (opers); + + if (opers) + access->sizarg = TREE_INT_CST_LOW (TREE_VALUE (opers)) - 1; + else + access->sizarg = UINT_MAX; + + access->ptr = NULL_TREE; + access->size = NULL_TREE; + access->attrs = TREE_CHAIN (access->attrs); + + /* When the last attribute/argument has been reached set ACCESS->ATTRS + to something other than TREE_LIST to indicate that to the next call. */ + if (!access->attrs && !access->argchain) + access->attrs = void_type_node; + + return true; +} + +/* Given a Gimple statement STMT that's a function call, initialize ACCESS + based on the called function's first access attribute specifier, if one + exists, or advance it to the next specifier if it's already been + initialized. Return true if ACCESS has been initialized or advanced + to a valid access specifier, false otherwise. */ + +bool +get_attr_access_ptr_and_size (const gimple *stmt, attr_access *access) +{ + const gcall *gc = as_a (stmt); + if (!gc) + return false; + + tree fntype = gimple_call_fntype (gc); + if (!fntype) + return false; + + if (!get_attr_access_ptr_and_size (fntype, access)) + return false; + + if (access->ptrarg < gimple_call_num_args (gc)) + { + tree ptr = gimple_call_arg (gc, access->ptrarg); + access->ptr = POINTER_TYPE_P (TREE_TYPE (ptr)) ? ptr : NULL_TREE; + } + else + access->ptr = NULL_TREE; + + if (access->sizarg <= gimple_call_num_args (gc)) + { + tree size = gimple_call_arg (gc, access->sizarg); + access->size = INTEGRAL_TYPE_P (TREE_TYPE (size)) ? size : NULL_TREE; + } + else + access->size = NULL_TREE; + + return access->ptr; +} + #if CHECKING_P diff --git a/gcc/attribs.h b/gcc/attribs.h index 23a7321e04a..657216d6584 100644 --- a/gcc/attribs.h +++ b/gcc/attribs.h @@ -218,4 +218,37 @@ lookup_attribute_by_prefix (const char *attr_name, tree list) } } +/* Description of a function argument declared attribute read_only, + read_write, or write_only. Used as an "iterator" over all such + arguments in a function declaration or call. */ + +struct attr_access +{ + /* Attribute chain for the given function declaration. */ + tree attrs; + + /* The attribute pointer argument. */ + tree ptr; + /* The size of the pointed-to object or NULL when not specified. */ + tree size; + + /* The tree node corresponding to the current argument in the chain + of formal function arguments in a call to the given function. + Used by attributes that specify that all relevant pointer + arguments are of the given kind without explicitly referencing + any size arguments, as in: + strcmp (const char*, const char*) __attribute__ ((read_only)) */ + tree argchain; + + /* The zero-based number of each of the formal function arguments. */ + unsigned ptrarg; + unsigned sizarg; + enum access_kind { read_only, write_only, read_write }; + + access_kind kind; +}; + +extern bool get_attr_access_ptr_and_size (tree, attr_access *); +extern bool get_attr_access_ptr_and_size (const gimple *, attr_access *); + #endif // GCC_ATTRIBS_H diff --git a/gcc/builtin-attrs.def b/gcc/builtin-attrs.def index 39d1395f42a..9cc46899561 100644 --- a/gcc/builtin-attrs.def +++ b/gcc/builtin-attrs.def @@ -119,6 +119,9 @@ DEF_ATTR_IDENT (ATTR_TM_TMPURE, "transaction_pure") DEF_ATTR_IDENT (ATTR_RETURNS_TWICE, "returns_twice") DEF_ATTR_IDENT (ATTR_RETURNS_NONNULL, "returns_nonnull") DEF_ATTR_IDENT (ATTR_WARN_UNUSED_RESULT, "warn_unused_result") +DEF_ATTR_IDENT (ATTR_READ_ONLY, "read_only") +DEF_ATTR_IDENT (ATTR_READ_WRITE, "read_write") +DEF_ATTR_IDENT (ATTR_WRITE_ONLY, "write_only") DEF_ATTR_TREE_LIST (ATTR_NOVOPS_LIST, ATTR_NOVOPS, ATTR_NULL, ATTR_NULL) @@ -212,6 +215,7 @@ DEF_ATTR_TREE_LIST (ATTR_NOTHROW_NONNULL, ATTR_NONNULL, ATTR_NULL, \ DEF_ATTR_TREE_LIST (ATTR_NOTHROW_NONNULL_LEAF, ATTR_NONNULL, ATTR_NULL, \ ATTR_NOTHROW_LEAF_LIST) DEF_ATTR_TREE_LIST (ATTR_NOTHROW_NONNULL_LEAF_LIST, ATTR_LEAF, ATTR_NULL, ATTR_NOTHROW_NONNULL_LEAF) + /* Nothrow functions whose first parameter is a nonnull pointer. */ DEF_ATTR_TREE_LIST (ATTR_NOTHROW_NONNULL_1, ATTR_NONNULL, ATTR_LIST_1, \ ATTR_NOTHROW_LIST) @@ -271,10 +275,54 @@ DEF_ATTR_TREE_LIST (ATTR_NOTHROW_NONNULL_TYPEGENERIC_LEAF, /* Nothrow const functions whose pointer parameter(s) are all nonnull. */ DEF_ATTR_TREE_LIST (ATTR_CONST_NOTHROW_NONNULL, ATTR_CONST, ATTR_NULL, \ ATTR_NOTHROW_NONNULL) + /* Nothrow leaf functions whose pointer parameter(s) are all nonnull, - and which return their first argument. */ + which return their first argument, and which access their arguments + for reading and writing as indicated by RDONLY and WRONLY. */ DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_NONNULL_LEAF, ATTR_FNSPEC, ATTR_LIST_STR1, \ ATTR_NOTHROW_NONNULL_LEAF) + +DEF_ATTR_TREE_LIST (ATTR_NOTHROW_WRONLY1_LEAF, ATTR_WRITE_ONLY, \ + ATTR_LIST_1, ATTR_NOTHROW_LEAF_LIST) + +DEF_ATTR_TREE_LIST (ATTR_NOTHROW_WRONLY1_2_LEAF, ATTR_WRITE_ONLY, \ + ATTR_LIST_1_2, ATTR_NOTHROW_LEAF_LIST) + +DEF_ATTR_TREE_LIST (ATTR_NOTHROW_WRONLY1_3_LEAF, ATTR_WRITE_ONLY, \ + ATTR_LIST_1_3, ATTR_NOTHROW_LEAF_LIST) + +DEF_ATTR_TREE_LIST (ATTR_NOTHROW_WRONLY2_3_LEAF, ATTR_WRITE_ONLY, \ + ATTR_LIST_2_3, ATTR_NOTHROW_NONNULL_LEAF) + +DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_WRONLY1_LEAF, ATTR_WRITE_ONLY, \ + ATTR_LIST_1, ATTR_RET1_NOTHROW_NONNULL_LEAF) + +DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_WRONLY1_3_LEAF, ATTR_WRITE_ONLY, \ + ATTR_LIST_1_3, ATTR_RET1_NOTHROW_NONNULL_LEAF) + +/* strcat. */ +DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_NONNULL_RDONLY2_LEAF, + ATTR_READ_ONLY, ATTR_LIST_2, + ATTR_RET1_NOTHROW_NONNULL_LEAF) +DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_NONNULL_RDWR1_RDONLY2_LEAF, + ATTR_READ_WRITE, ATTR_LIST_1, + ATTR_RET1_NOTHROW_NONNULL_RDONLY2_LEAF) + +/* memcpy, memmove. */ +DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_3_LEAF, + ATTR_READ_ONLY, ATTR_LIST_2_3, + ATTR_RET1_NOTHROW_WRONLY1_3_LEAF) + +/* strcpy. */ +DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_WRONLY1_RDONLY2_LEAF, + ATTR_READ_ONLY, ATTR_LIST_2, + ATTR_RET1_NOTHROW_WRONLY1_LEAF) + +/* strncpy. */ +DEF_ATTR_TREE_LIST (ATTR_RET1_NOTHROW_WRONLY1_3_RDONLY2_LEAF, + ATTR_READ_ONLY, ATTR_LIST_2, + ATTR_RET1_NOTHROW_WRONLY1_3_LEAF) + /* Nothrow leaf functions whose pointer parameter(s) are all nonnull, and return value is also nonnull. */ DEF_ATTR_TREE_LIST (ATTR_RETNONNULL_NOTHROW_LEAF, ATTR_RETURNS_NONNULL, ATTR_NULL, \ @@ -305,6 +353,52 @@ DEF_ATTR_TREE_LIST (ATTR_WARN_UNUSED_RESULT_NOTHROW_NONNULL_LEAF, ATTR_WARN_UNUS DEF_ATTR_TREE_LIST (ATTR_MALLOC_WARN_UNUSED_RESULT_NOTHROW_NONNULL_LEAF, ATTR_MALLOC, ATTR_NULL, \ ATTR_WARN_UNUSED_RESULT_NOTHROW_NONNULL_LEAF) +/* strdup, strndup. */ +DEF_ATTR_TREE_LIST (ATTR_MALLOC_NOTHROW_NONNULL_RDONLY1_LEAF, + ATTR_READ_ONLY, ATTR_LIST_1, + ATTR_MALLOC_WARN_UNUSED_RESULT_NOTHROW_NONNULL_LEAF) + +/* strchr, strlen. */ +DEF_ATTR_TREE_LIST (ATTR_PURE_NOTHROW_NONNULL_RDONLY1_LEAF, + ATTR_READ_ONLY, ATTR_LIST_1, + ATTR_PURE_NOTHROW_NONNULL_LEAF) + +/* strcmp, strstr. */ +DEF_ATTR_TREE_LIST (ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF, + ATTR_READ_ONLY, ATTR_LIST_2, + ATTR_PURE_NOTHROW_NONNULL_RDONLY1_LEAF) + +/* mempcpy. */ +DEF_ATTR_TREE_LIST (ATTR_RETNONNULL_RDONLY2_3_NOTHROW_LEAF, + ATTR_READ_ONLY, ATTR_LIST_2_3, + ATTR_RETNONNULL_NOTHROW_LEAF) +DEF_ATTR_TREE_LIST (ATTR_RETNONNULL_WRONLY1_3_RDONLY2_3_NOTHROW_LEAF, + ATTR_WRITE_ONLY, ATTR_LIST_1_3, + ATTR_RETNONNULL_RDONLY2_3_NOTHROW_LEAF) + +/* memchr. */ +DEF_ATTR_TREE_LIST (ATTR_PURE_NOTHROW_NONNULL_RDONLY1_3_LEAF, + ATTR_READ_ONLY, ATTR_LIST_1_3, + ATTR_NOTHROW_NONNULL_LEAF) + +/* memcmp. */ +DEF_ATTR_TREE_LIST (ATTR_PURE_NOTHROW_NONNULL_RDONLY1_2_3_LEAF, + ATTR_READ_ONLY, ATTR_LIST_2_3, \ + ATTR_PURE_NOTHROW_NONNULL_RDONLY1_3_LEAF) + +/* stpcpy. */ +DEF_ATTR_TREE_LIST (ATTR_RETNONNULL_RDONLY2_NOTHROW_LEAF, + ATTR_READ_ONLY, ATTR_LIST_2, + ATTR_RETNONNULL_NOTHROW_LEAF) +DEF_ATTR_TREE_LIST (ATTR_RETNONNULL_WRONLY1_RDONLY2_NOTHROW_LEAF, + ATTR_WRITE_ONLY, ATTR_LIST_1, + ATTR_RETNONNULL_RDONLY2_NOTHROW_LEAF) + +/* stpncpy. */ +DEF_ATTR_TREE_LIST (ATTR_RETNONNULL_WRONLY1_3_RDONLY2_NOTHROW_LEAF, + ATTR_READ_ONLY, ATTR_LIST_2, + ATTR_RETNONNULL_RDONLY2_NOTHROW_LEAF) + /* Construct a tree for the format attribute (and implicitly nonnull). */ #define DEF_FORMAT_ATTRIBUTE(TYPE, FA, VALUES) \ DEF_ATTR_TREE_LIST (ATTR_##TYPE##_##VALUES, ATTR_NULL, \ diff --git a/gcc/builtins.c b/gcc/builtins.c index f94151bd84d..e300cdb649d 100644 --- a/gcc/builtins.c +++ b/gcc/builtins.c @@ -3299,7 +3299,7 @@ determine_block_size (tree len, rtx len_rtx, If the call is successfully verified as safe return true, otherwise return false. */ -static bool +bool check_access (tree exp, tree, tree, tree dstwrite, tree maxread, tree srcstr, tree dstsize) { @@ -3433,37 +3433,42 @@ check_access (tree exp, tree, tree, tree dstwrite, location_t loc = tree_nonartificial_location (exp); loc = expansion_point_location_if_in_system_header (loc); + bool warned = false; if (dstwrite == slen && at_least_one) { /* This is a call to strcpy with a destination of 0 size and a source of unknown length. The call will write at least one byte past the end of the destination. */ - warning_at (loc, opt, - "%K%qD writing %E or more bytes into a region " - "of size %E overflows the destination", - exp, func, range[0], dstsize); + warned = warning_at (loc, opt, + "%K%qD writing %E or more bytes into " + "a region of size %E overflows " + "the destination", + exp, func, range[0], dstsize); } else if (tree_int_cst_equal (range[0], range[1])) - warning_n (loc, opt, tree_to_uhwi (range[0]), - "%K%qD writing %E byte into a region " - "of size %E overflows the destination", - "%K%qD writing %E bytes into a region " - "of size %E overflows the destination", - exp, func, range[0], dstsize); + warned = warning_n (loc, opt, tree_to_uhwi (range[0]), + "%K%qD writing %E byte into a region " + "of size %E overflows the destination", + "%K%qD writing %E bytes into a region " + "of size %E overflows the destination", + exp, func, range[0], dstsize); else if (tree_int_cst_sign_bit (range[1])) { /* Avoid printing the upper bound if it's invalid. */ - warning_at (loc, opt, - "%K%qD writing %E or more bytes into a region " - "of size %E overflows the destination", - exp, func, range[0], dstsize); + warned = warning_at (loc, opt, + "%K%qD writing %E or more bytes into " + "a region of size %E overflows " + "the destination", + exp, func, range[0], dstsize); } else - warning_at (loc, opt, - "%K%qD writing between %E and %E bytes into " - "a region of size %E overflows the destination", - exp, func, range[0], range[1], - dstsize); + warned = warning_at (loc, opt, + "%K%qD writing between %E and %E bytes into " + "a region of size %E overflows the destination", + exp, func, range[0], range[1], + dstsize); + if (warned) + TREE_NO_WARNING (exp) = true; /* Return error when an overflow has been detected. */ return false; @@ -3486,21 +3491,26 @@ check_access (tree exp, tree, tree, tree dstwrite, if (TREE_NO_WARNING (exp)) return false; + bool warned = false; + /* Warn about crazy big sizes first since that's more likely to be meaningful than saying that the bound is greater than the object size if both are big. */ if (range[0] == range[1]) - warning_at (loc, opt, - "%K%qD specified bound %E " - "exceeds maximum object size %E", - exp, func, - range[0], maxobjsize); + warned = warning_at (loc, opt, + "%K%qD specified bound %E " + "exceeds maximum object size %E", + exp, func, + range[0], maxobjsize); else - warning_at (loc, opt, - "%K%qD specified bound between %E and %E " - "exceeds maximum object size %E", - exp, func, - range[0], range[1], maxobjsize); + warned = warning_at (loc, opt, + "%K%qD specified bound between %E and %E " + "exceeds maximum object size %E", + exp, func, + range[0], range[1], maxobjsize); + + if (warned) + TREE_NO_WARNING (exp) = true; return false; } @@ -3510,18 +3520,23 @@ check_access (tree exp, tree, tree, tree dstwrite, if (TREE_NO_WARNING (exp)) return false; + bool warned = false; + if (tree_int_cst_equal (range[0], range[1])) - warning_at (loc, opt, - "%K%qD specified bound %E " - "exceeds destination size %E", - exp, func, - range[0], dstsize); + warned = warning_at (loc, opt, + "%K%qD specified bound %E " + "exceeds destination size %E", + exp, func, + range[0], dstsize); else - warning_at (loc, opt, - "%K%qD specified bound between %E and %E " - "exceeds destination size %E", - exp, func, - range[0], range[1], dstsize); + warned = warning_at (loc, opt, + "%K%qD specified bound between %E and %E " + "exceeds destination size %E", + exp, func, + range[0], range[1], dstsize); + if (warned) + TREE_NO_WARNING (exp) = true; + return false; } } @@ -3536,26 +3551,31 @@ check_access (tree exp, tree, tree, tree dstwrite, if (TREE_NO_WARNING (exp)) return false; + bool warned = false; location_t loc = tree_nonartificial_location (exp); + loc = expansion_point_location_if_in_system_header (loc); if (tree_int_cst_equal (range[0], range[1])) - warning_n (loc, opt, tree_to_uhwi (range[0]), - "%K%qD reading %E byte from a region of size %E", - "%K%qD reading %E bytes from a region of size %E", - exp, func, range[0], slen); + warned = warning_n (loc, opt, tree_to_uhwi (range[0]), + "%K%qD reading %E byte from a region of size %E", + "%K%qD reading %E bytes from a region of size %E", + exp, func, range[0], slen); else if (tree_int_cst_sign_bit (range[1])) { /* Avoid printing the upper bound if it's invalid. */ - warning_at (loc, opt, - "%K%qD reading %E or more bytes from a region " - "of size %E", - exp, func, range[0], slen); + warned = warning_at (loc, opt, + "%K%qD reading %E or more bytes from a region " + "of size %E", + exp, func, range[0], slen); } else - warning_at (loc, opt, - "%K%qD reading between %E and %E bytes from a region " - "of size %E", - exp, func, range[0], range[1], slen); + warned = warning_at (loc, opt, + "%K%qD reading between %E and %E bytes from " + "a region of size %E", + exp, func, range[0], range[1], slen); + if (warned) + TREE_NO_WARNING (exp) = true; + return false; } diff --git a/gcc/builtins.def b/gcc/builtins.def index d8233f5f760..e24cd31e7ca 100644 --- a/gcc/builtins.def +++ b/gcc/builtins.def @@ -722,9 +722,9 @@ DEF_LIB_BUILTIN (BUILT_IN_STRNCMP, "strncmp", BT_FN_INT_CONST_STRING_CONS DEF_LIB_BUILTIN (BUILT_IN_STRNCPY, "strncpy", BT_FN_STRING_STRING_CONST_STRING_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF) DEF_EXT_LIB_BUILTIN (BUILT_IN_STRNLEN, "strnlen", BT_FN_SIZE_CONST_STRING_SIZE, ATTR_PURE_NOTHROW_NONNULL_LEAF) DEF_LIB_BUILTIN (BUILT_IN_STRPBRK, "strpbrk", BT_FN_STRING_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_LEAF) -DEF_LIB_BUILTIN (BUILT_IN_STRRCHR, "strrchr", BT_FN_STRING_CONST_STRING_INT, ATTR_PURE_NOTHROW_NONNULL_LEAF) -DEF_LIB_BUILTIN (BUILT_IN_STRSPN, "strspn", BT_FN_SIZE_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_LEAF) -DEF_LIB_BUILTIN (BUILT_IN_STRSTR, "strstr", BT_FN_STRING_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_LEAF) +DEF_LIB_BUILTIN (BUILT_IN_STRRCHR, "strrchr", BT_FN_STRING_CONST_STRING_INT, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_LEAF) +DEF_LIB_BUILTIN (BUILT_IN_STRSPN, "strspn", BT_FN_SIZE_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF) +DEF_LIB_BUILTIN (BUILT_IN_STRSTR, "strstr", BT_FN_STRING_CONST_STRING_CONST_STRING, ATTR_PURE_NOTHROW_NONNULL_RDONLY1_RDONLY2_LEAF) /* Category: stdio builtins. */ DEF_LIB_BUILTIN (BUILT_IN_FPRINTF, "fprintf", BT_FN_INT_FILEPTR_CONST_STRING_VAR, ATTR_NONNULL_1_FORMAT_PRINTF_2_3) @@ -870,7 +870,7 @@ DEF_EXT_LIB_BUILTIN (BUILT_IN_FFSLL, "ffsll", BT_FN_INT_LONGLONG, ATTR_CONST_ DEF_EXT_LIB_BUILTIN (BUILT_IN_FORK, "fork", BT_FN_PID, ATTR_NOTHROW_LIST) DEF_GCC_BUILTIN (BUILT_IN_FRAME_ADDRESS, "frame_address", BT_FN_PTR_UINT, ATTR_NULL) /* [trans-mem]: Adjust BUILT_IN_TM_FREE if BUILT_IN_FREE is changed. */ -DEF_LIB_BUILTIN (BUILT_IN_FREE, "free", BT_FN_VOID_PTR, ATTR_NOTHROW_LEAF_LIST) +DEF_LIB_BUILTIN (BUILT_IN_FREE, "free", BT_FN_VOID_PTR, ATTR_NOTHROW_WRONLY1_LEAF) DEF_GCC_BUILTIN (BUILT_IN_FROB_RETURN_ADDR, "frob_return_addr", BT_FN_PTR_PTR, ATTR_NULL) DEF_EXT_LIB_BUILTIN (BUILT_IN_GETTEXT, "gettext", BT_FN_STRING_CONST_STRING, ATTR_FORMAT_ARG_1) DEF_C99_BUILTIN (BUILT_IN_IMAXABS, "imaxabs", BT_FN_INTMAX_INTMAX, ATTR_CONST_NOTHROW_LEAF_LIST) @@ -967,16 +967,16 @@ DEF_BUILTIN_STUB (BUILT_IN_STRNCMP_EQ, "__builtin_strncmp_eq") /* Object size checking builtins. */ DEF_GCC_BUILTIN (BUILT_IN_OBJECT_SIZE, "object_size", BT_FN_SIZE_CONST_PTR_INT, ATTR_PURE_NOTHROW_LEAF_LIST) -DEF_EXT_LIB_BUILTIN (BUILT_IN_MEMCPY_CHK, "__memcpy_chk", BT_FN_PTR_PTR_CONST_PTR_SIZE_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF) -DEF_EXT_LIB_BUILTIN (BUILT_IN_MEMMOVE_CHK, "__memmove_chk", BT_FN_PTR_PTR_CONST_PTR_SIZE_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF) +DEF_EXT_LIB_BUILTIN (BUILT_IN_MEMCPY_CHK, "__memcpy_chk", BT_FN_PTR_PTR_CONST_PTR_SIZE_SIZE, ATTR_RET1_NOTHROW_WRONLY1_3_LEAF) +DEF_EXT_LIB_BUILTIN (BUILT_IN_MEMMOVE_CHK, "__memmove_chk", BT_FN_PTR_PTR_CONST_PTR_SIZE_SIZE, ATTR_RET1_NOTHROW_WRONLY1_3_LEAF) DEF_EXT_LIB_BUILTIN (BUILT_IN_MEMPCPY_CHK, "__mempcpy_chk", BT_FN_PTR_PTR_CONST_PTR_SIZE_SIZE, ATTR_RETNONNULL_NOTHROW_LEAF) -DEF_EXT_LIB_BUILTIN (BUILT_IN_MEMSET_CHK, "__memset_chk", BT_FN_PTR_PTR_INT_SIZE_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF) +DEF_EXT_LIB_BUILTIN (BUILT_IN_MEMSET_CHK, "__memset_chk", BT_FN_PTR_PTR_INT_SIZE_SIZE, ATTR_RET1_NOTHROW_WRONLY1_3_LEAF) DEF_EXT_LIB_BUILTIN (BUILT_IN_STPCPY_CHK, "__stpcpy_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE, ATTR_RETNONNULL_NOTHROW_LEAF) DEF_EXT_LIB_BUILTIN (BUILT_IN_STPNCPY_CHK, "__stpncpy_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE_SIZE, ATTR_RETNONNULL_NOTHROW_LEAF) DEF_EXT_LIB_BUILTIN (BUILT_IN_STRCAT_CHK, "__strcat_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF) -DEF_EXT_LIB_BUILTIN (BUILT_IN_STRCPY_CHK, "__strcpy_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF) +DEF_EXT_LIB_BUILTIN (BUILT_IN_STRCPY_CHK, "__strcpy_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE, ATTR_RET1_NOTHROW_WRONLY1_LEAF) DEF_EXT_LIB_BUILTIN (BUILT_IN_STRNCAT_CHK, "__strncat_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF) -DEF_EXT_LIB_BUILTIN (BUILT_IN_STRNCPY_CHK, "__strncpy_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE_SIZE, ATTR_RET1_NOTHROW_NONNULL_LEAF) +DEF_EXT_LIB_BUILTIN (BUILT_IN_STRNCPY_CHK, "__strncpy_chk", BT_FN_STRING_STRING_CONST_STRING_SIZE_SIZE, ATTR_RET1_NOTHROW_WRONLY1_3_LEAF) DEF_EXT_LIB_BUILTIN (BUILT_IN_SNPRINTF_CHK, "__snprintf_chk", BT_FN_INT_STRING_SIZE_INT_SIZE_CONST_STRING_VAR, ATTR_FORMAT_PRINTF_NOTHROW_5_6) DEF_EXT_LIB_BUILTIN (BUILT_IN_SPRINTF_CHK, "__sprintf_chk", BT_FN_INT_STRING_INT_SIZE_CONST_STRING_VAR, ATTR_NOTHROW_NONNULL_1_FORMAT_PRINTF_4_5) DEF_EXT_LIB_BUILTIN (BUILT_IN_VSNPRINTF_CHK, "__vsnprintf_chk", BT_FN_INT_STRING_SIZE_INT_SIZE_CONST_STRING_VALIST_ARG, ATTR_FORMAT_PRINTF_NOTHROW_5_0) diff --git a/gcc/builtins.h b/gcc/builtins.h index 1ad82e86963..2deb603059b 100644 --- a/gcc/builtins.h +++ b/gcc/builtins.h @@ -151,5 +151,7 @@ extern internal_fn replacement_internal_fn (gcall *); extern void warn_string_no_nul (location_t, const char *, tree, tree); extern tree unterminated_array (tree, tree * = NULL, bool * = NULL); extern bool builtin_with_linkage_p (tree); +extern bool check_access (tree, tree, tree, tree, tree, tree, tree); + #endif /* GCC_BUILTINS_H */ diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c index c62cebf7bfd..eff430e267e 100644 --- a/gcc/c-family/c-attribs.c +++ b/gcc/c-family/c-attribs.c @@ -125,6 +125,10 @@ static tree handle_nothrow_attribute (tree *, tree, tree, int, bool *); static tree handle_cleanup_attribute (tree *, tree, tree, int, bool *); static tree handle_warn_unused_result_attribute (tree *, tree, tree, int, bool *); +static tree handle_read_only_attribute (tree *, tree, tree, int, bool *); +static tree handle_read_write_attribute (tree *, tree, tree, int, bool *); +static tree handle_write_only_attribute (tree *, tree, tree, int, bool *); + static tree handle_sentinel_attribute (tree *, tree, tree, int, bool *); static tree handle_type_generic_attribute (tree *, tree, tree, int, bool *); static tree handle_alloc_size_attribute (tree *, tree, tree, int, bool *); @@ -480,6 +484,12 @@ const struct attribute_spec c_common_attribute_table[] = handle_copy_attribute, NULL }, { "noinit", 0, 0, true, false, false, false, handle_noinit_attribute, attr_noinit_exclusions }, + { "read_only", 0, 2, false, true, true, false, + handle_read_only_attribute, NULL }, + { "write_only", 0, 2, false, true, true, false, + handle_write_only_attribute, NULL }, + { "read_write", 0, 2, false, true, true, false, + handle_read_write_attribute, NULL }, { NULL, 0, 0, false, false, false, false, NULL, NULL } }; @@ -3798,6 +3808,228 @@ handle_nonstring_attribute (tree *node, tree name, tree ARG_UNUSED (args), return NULL_TREE; } +/* Given a function type FUNCTYPE, returns the type of the parameter + ARGNO or null if ARGNO exceeds the number of parameters. On failure + set *NARGS to the number of function parameters. */ + +static tree +get_argument_type (tree functype, unsigned argno, unsigned *nargs) +{ + function_args_iterator iter; + function_args_iter_init (&iter, functype); + + unsigned count = 0; + + for ( ; ; ++count, function_args_iter_next (&iter)) + { + if (count + 1 == argno) + { + tree argtype = function_args_iter_cond (&iter); + if (VOID_TYPE_P (argtype)) + break; + return argtype; + } + } + + *nargs = count; + return NULL_TREE; +} + +/* Handle attributes read_only, write_only, and read_write. */ + +static tree +handle_rdwr_attributes (bool writeable, tree *node, tree name, + tree operands, int ARG_UNUSED (flags), + bool *no_add_attrs) +{ + tree type = *node; + tree attrs = TYPE_ATTRIBUTES (type); + + /* If no operands are specified, all non-const pointer arguments are + treated as write-only. Verify a full prototype is given so that + the arguments will have the correct types when we actually check + them later. Avoid diagnosing type-generic built-ins since those + have no prototype. */ + if (!operands + && !prototype_p (type) + && (!attrs || !lookup_attribute ("type generic", attrs))) + { + error ("attribute %qE without arguments on a non-prototype", name); + *no_add_attrs = true; + return NULL_TREE; + } + + /* Attribute operands have been specified. Verify that each operand + value references a non-const pointer argument to the function. */ + tree idxnodes[2] = { NULL_TREE, NULL_TREE }; + tree argtypes[2] = { NULL_TREE, NULL_TREE }; + unsigned HOST_WIDE_INT idxs[2] = { 0, 0 }; + + /* Number of function formal arguments (used in diagnostics). */ + unsigned argcount = 0; + /* Number of attribute operands. */ + unsigned opcount = 0; + + for (unsigned i = 0; i != 2; ++i, operands = TREE_CHAIN (operands), ++opcount) + { + if (!operands) + break; + + idxnodes[i] = TREE_VALUE (operands); + + if (TREE_CODE (idxnodes[i]) != IDENTIFIER_NODE + && TREE_CODE (idxnodes[i]) != FUNCTION_DECL) + idxnodes[i] = default_conversion (idxnodes[i]); + + if (!get_attribute_operand (idxnodes[i], idxs + i)) + { + *no_add_attrs = true; + idxs[i] = i + 1; + } + + argtypes[i] = get_argument_type (type, idxs[i], &argcount); + } + + if (*no_add_attrs) + { + /* Dignose an invalid operand and bail. */ + if (idxnodes[1]) + error ("attribute %<%E(%E, %E)%> invalid operand %wu", + name, idxnodes[0], idxnodes[1], idxs[0] ? idxs[0] : idxs[1]); + else + error ("attribute %<%E(%E)%> invalid operand", + name, idxnodes[0]); + + return NULL_TREE; + } + + if (!argtypes[0] || (idxnodes[1] && !argtypes[1])) + { + if (idxnodes[1]) + error ("attribute %<%E(%E, %E)%> operand exceeds number " + "of arguments %u", + name, idxnodes[0], idxnodes[1], argcount); + else if (idxnodes[0]) + error ("attribute %<%E(%E)%> operand exceeds number of arguments %u", + name, idxnodes[0], argcount); + else if (!opcount) + { + /* FIXME: reject empty attribute write_only on function + declarations with no non-const object pointer arguments. */ + tree arg; + function_args_iterator it; + FOREACH_FUNCTION_ARGS (*node, arg, it) + if (POINTER_TYPE_P (arg)) + return NULL_TREE; + + error ("attribute %<%E%> on a function with no pointer arguments", + name); + *no_add_attrs = true; + return NULL_TREE; + } + else + error ("attribute %<%E%> invalid operand", + name); + + *no_add_attrs = true; + return NULL_TREE; + } + + if (TREE_CODE (argtypes[0]) != POINTER_TYPE) + { + if (idxnodes[1]) + error ("attribute %<%E(%E, %E)%> first operand references " + "non-pointer argument type %qT", + name, idxnodes[0], idxnodes[1], argtypes[0]); + else + error ("attribute %<%E(%E)%> references non-pointer argument " + "type %qT", + name, idxnodes[0], argtypes[0]); + *no_add_attrs = true; + return NULL_TREE; + } + + if (writeable) + { + /* A read_write and write_only attributes must reference non-const + arguments. */ + if (TYPE_READONLY (TREE_TYPE (argtypes[0]))) + { + if (idxnodes[1]) + error ("attribute %<%E(%E, %E)%> first operand references " + "%qs-qualified argument type %qT", + name, idxnodes[0], idxnodes[1], "const", argtypes[0]); + else + error ("attribute %<%E(%E)%> operand references %qs-qualified " + "argument type %qT", + name, idxnodes[0], "const", argtypes[0]); + *no_add_attrs = true; + return NULL_TREE; + } + } + else if (!TYPE_READONLY (TREE_TYPE (argtypes[0]))) + { + /* A read_only attributes should reference const-qualified arguments + but it's not an error if one doesn't. */ + if (idxnodes[1]) + warning (OPT_Wattributes, + "attribute %<%E(%E, %E)%> first operand references " + "non-%qs argument type %qT", + name, idxnodes[0], idxnodes[1], "const", argtypes[0]); + else + warning (OPT_Wattributes, + "attribute %<%E(%E)%> operand references non-%qs " + "argument type %qT", + name, idxnodes[0], "const", argtypes[0]); + } + + if (argtypes[1] && !INTEGRAL_TYPE_P (argtypes[1])) + { + if (idxnodes[1]) + error ("attribute %<%E(%E, %E)%> second operand references " + "non-integer argument type %qT", + name, idxnodes[0], idxnodes[1], argtypes[1]); + else + error ("attribute %<%E(%E)%> second operand references " + "non-integer argument type %qT", + name, idxnodes[0], argtypes[1]); + *no_add_attrs = true; + return NULL_TREE; + } + + return NULL_TREE; +} + +/* Handle attribute read_only ([ptrarg [, sizarg]]). */ + +static tree +handle_read_only_attribute (tree *node, tree name, tree operands, int flags, + bool *no_add_attrs) +{ + return handle_rdwr_attributes (false, node, name, operands, flags, + no_add_attrs); +} + +/* Handle attribute write_only ([ptrarg [, sizarg]]). */ + +static tree +handle_write_only_attribute (tree *node, tree name, tree operands, int flags, + bool *no_add_attrs) +{ + return handle_rdwr_attributes (true, node, name, operands, flags, + no_add_attrs); +} + +/* Handle attribute read_write ([ptrarg [, sizarg]]). */ + +static tree +handle_read_write_attribute (tree *node, tree name, + tree operands, int flags, bool *no_add_attrs) +{ + return handle_rdwr_attributes (true, node, name, operands, flags, + no_add_attrs); +} + /* Handle a "nothrow" attribute; arguments as in struct attribute_spec.handler. */ diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c index 48811994f38..4d143adb601 100644 --- a/gcc/c-family/c-common.c +++ b/gcc/c-family/c-common.c @@ -5483,7 +5483,7 @@ nonnull_check_p (tree args, unsigned HOST_WIDE_INT param_num) for (; args; args = TREE_CHAIN (args)) { - bool found = get_nonnull_operand (TREE_VALUE (args), &arg_num); + bool found = get_attribute_operand (TREE_VALUE (args), &arg_num); gcc_assert (found); @@ -5518,11 +5518,11 @@ check_nonnull_arg (void *ctx, tree param, unsigned HOST_WIDE_INT param_num) } } -/* Helper for nonnull attribute handling; fetch the operand number - from the attribute argument list. */ +/* Helper for attribute handling; fetch the operand number from + the attribute argument list. */ bool -get_nonnull_operand (tree arg_num_expr, unsigned HOST_WIDE_INT *valp) +get_attribute_operand (tree arg_num_expr, unsigned HOST_WIDE_INT *valp) { /* Verify the arg number is a small constant. */ if (tree_fits_uhwi_p (arg_num_expr)) diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h index 80a8c9f4543..285f0f11dad 100644 --- a/gcc/c-family/c-common.h +++ b/gcc/c-family/c-common.h @@ -880,7 +880,7 @@ extern bool pointer_to_zero_sized_aggr_p (tree); extern bool bool_promoted_to_int_p (tree); extern tree fold_for_warn (tree); extern tree c_common_get_narrower (tree, int *); -extern bool get_nonnull_operand (tree, unsigned HOST_WIDE_INT *); +extern bool get_attribute_operand (tree, unsigned HOST_WIDE_INT *); #define c_sizeof(LOC, T) c_sizeof_or_alignof_type (LOC, T, true, false, 1) #define c_alignof(LOC, T) c_sizeof_or_alignof_type (LOC, T, false, false, 1) diff --git a/gcc/calls.c b/gcc/calls.c index 62921351b11..316a2a1281c 100644 --- a/gcc/calls.c +++ b/gcc/calls.c @@ -52,6 +52,8 @@ along with GCC; see the file COPYING3. If not see #include "tree-ssa-strlen.h" #include "intl.h" #include "stringpool.h" +#include "hash-map.h" +#include "hash-traits.h" #include "attribs.h" #include "builtins.h" #include "gimple-fold.h" @@ -1870,6 +1872,273 @@ maybe_complain_about_tail_call (tree call_expr, const char *reason) error_at (EXPR_LOCATION (call_expr), "cannot tail-call: %s", reason); } +/* Used to define rdwr_map below. */ +struct rdwr_access_hash: int_hash { }; + +/* A mapping between argument number corresponding to attribute read_only, + write_only, or read_write operand and the read/write specification. */ +typedef hash_map rdwr_map; + +/* Initialize a mapping for a call to function FNDECL declared with + attributes read_only, write_only, or read_write. Each attribute + operand inserts one entry into the mapping with the operand number + as the key. */ + +static void +init_attr_rdwr_indices (rdwr_map *rwm, tree fndecl) +{ + if (!fndecl) + return; + + /* If the function's type has no attributes there's nothing to do. */ + tree fntype = TREE_TYPE (fndecl); + if (!TYPE_ATTRIBUTES (fntype)) + return; + + /* Iterate over arguments to functions declared with attribute + read_only or read_write and initialize those that are not + initialized. */ + const attr_access::access_kind access_kinds[] = { + attr_access::read_only, attr_access::read_write, attr_access::write_only + }; + + for (unsigned i = 0; i != sizeof access_kinds / sizeof *access_kinds; ++i) + { + attr_access acc = { }; + acc.kind = access_kinds[i]; + while (get_attr_access_ptr_and_size (fntype, &acc)) + { + /* Unconditionally add an entry for the required pointer + operand of the attribute, and one for the optional size + operand when it's specified. */ + rwm->put (acc.ptrarg, acc); + if (acc.sizarg != UINT_MAX) + rwm->put (acc.sizarg, acc); + } + } +} + +/* Returns the type of the argument ARGNO to function with type FNTYPE + or null when the typoe cannot be determined or no such argument exists. */ + +static tree +fntype_argno_type (tree fntype, unsigned argno) +{ + if (!prototype_p (fntype)) + return NULL_TREE; + + tree argtype; + function_args_iterator it; + FOREACH_FUNCTION_ARGS (fntype, argtype, it) + if (argno-- == 0) + return argtype; + + return NULL_TREE; +} + +/* Helper to append the "rdwr" attribute specification described + by ACCESS to the array ATTRSTR with size STRSIZE. */ + +static inline void +append_attrname (const std::pair &access, + char *attrstr, size_t strsize) +{ + /* Append the relevant attribute to the string. This (deliberately) + appends the attribute pointer operand even when none was specified. */ + size_t len = strlen (attrstr); + + const char *atname + = (access.second.kind == attr_access::read_only + ? "read_only" + : (access.second.kind == attr_access::write_only + ? "write_only" : "read_write")); + + const char *sep = len ? ", " : ""; + + if (access.second.sizarg == UINT_MAX) + snprintf (attrstr + len, strsize - len, + "%s%s (%i)", sep, atname, + access.second.ptrarg + 1); + else + snprintf (attrstr + len, strsize - len, + "%s%s (%i, %i)", sep, atname, + access.second.ptrarg + 1, access.second.sizarg + 1); +} + +/* Iterate over read-only, read-write, and write-only arguments and + diagnose past-the-end accesses and related problems in the function + call EXP. */ + +static void +maybe_warn_rdwr_sizes (rdwr_map *rwm, tree exp) +{ + /* A string describing the attributes that the warnings issued + by this function apply to. Used to print one informational + note per function call, rather than one per warning. That + reduces clutter. */ + char attrstr[80]; + attrstr[0] = 0; + + const tree fndecl = TREE_OPERAND (CALL_EXPR_FN (exp), 0); + + for (rdwr_map::iterator it = rwm->begin (); it != rwm->end (); ++it) + { + std::pair access = *it; + + /* Get the function call arguments corresponding to the attribute's + first and (optionally) second operands. When both operands have + been specified there will be two entries in *RWM, one for each. + They are cross-referenced by their respective argument numbers + in ACCESS.PTRARG and ACCESS.SIZARG. */ + const int ptridx = access.second.ptrarg; + const int sizidx = access.second.sizarg; + + gcc_assert (ptridx != -1); + gcc_assert (access.first == ptridx || access.first == sizidx); + + /* The pointer is set to null for the entry corresponding to + the size argument. Skip it. It's handled when the entry + corresponding to the pointer argument comes up. */ + if (!access.second.ptr) + continue; + + tree argtype = fntype_argno_type (TREE_TYPE (fndecl), ptridx); + argtype = TREE_TYPE (argtype); + + tree size; + if (sizidx == -1) + { + /* If only the pointer attribute operand was specified + and not size, set SIZE to the size of one element of + the pointed to type to detect smaller objects (null + pointers are diagnosed in this case only if + the pointer is also declared with attribute nonnull. */ + size = size_one_node; + } + else + size = rwm->get (sizidx)->size; + + tree ptr = access.second.ptr; + tree sizrng[2]; + if (get_size_range (size, sizrng, true) + && tree_int_cst_sgn (sizrng[0]) < 0 + && tree_int_cst_sgn (sizrng[1]) < 0) + { + /* Warn about negative sizes. */ + bool warned = false; + location_t loc = EXPR_LOCATION (exp); + if (tree_int_cst_equal (sizrng[0], sizrng[1])) + warned = warning_at (loc, OPT_Wstringop_overflow_, + "%Kargument %i value %E is negative", + exp, sizidx + 1, size); + else + warned = warning_at (loc, OPT_Wstringop_overflow_, + "%Kargument %i range [%E, %E] is negative", + exp, sizidx + 1, sizrng[0], sizrng[1]); + if (warned) + { + append_attrname (access, attrstr, sizeof attrstr); + /* Avoid warning again for the same attribute. */ + continue; + } + } + + if (tree_int_cst_sgn (sizrng[0]) >= 0) + { + if (COMPLETE_TYPE_P (argtype)) + { + /* Multiple SIZE by the size of the type the pointer + argument points to. If it's incomplete the size + is used as is. */ + size = NULL_TREE; + if (tree argsize = TYPE_SIZE_UNIT (argtype)) + if (TREE_CODE (argsize) == INTEGER_CST) + { + const int prec = TYPE_PRECISION (sizetype); + wide_int minsize = wi::to_wide (sizrng[0], prec); + minsize *= wi::to_wide (argsize, prec); + size = wide_int_to_tree (sizetype, minsize); + } + } + } + else + size = NULL_TREE; + + if (sizidx >= 0 + && integer_zerop (ptr) + && tree_int_cst_sgn (sizrng[0]) > 0) + { + /* Warn about null pointers with positive sizes. This is + different from also declaring the pointer argument with + attribute nonnull when the function accepts null pointers + only when the corresponding size is zero. */ + bool warned = false; + location_t loc = EXPR_LOCATION (exp); + if (tree_int_cst_equal (sizrng[0], sizrng[1])) + warned = warning_at (loc, OPT_Wnonnull, + "%Kargument %i is null but the corresponding " + "size argument %i value is %E", + exp, ptridx + 1, sizidx + 1, size); + else + warned = warning_at (loc, OPT_Wnonnull, + "%Kargument %i is null but the corresponding " + "size argument %i range is [%E, %E]", + exp, ptridx + 1, sizidx + 1, + sizrng[0], sizrng[1]); + if (warned) + { + append_attrname (access, attrstr, sizeof attrstr); + /* Avoid warning again for the same attribute. */ + continue; + } + } + + tree objsize = compute_objsize (ptr, 0); + + tree srcsize; + if (access.second.kind == attr_access::write_only) + { + /* For a write-only argument there is no source. */ + srcsize = NULL_TREE; + } + else + { + /* For read-only and read-write attributes also set the source + size. */ + srcsize = objsize; + if (access.second.kind == attr_access::read_only) + { + /* For a read-only attribute there is no destination so + clear OBJSIZE. This emits "reading N bytes" kind of + diagnostics instead of the "writing N bytes" kind. */ + objsize = NULL_TREE; + } + } + + /* Clear the no-warning bit in case it was set in a prior + iteration so that accesses via different arguments are + diagnosed. */ + TREE_NO_WARNING (exp) = false; + check_access (exp, NULL_TREE, NULL_TREE, size, /*maxread=*/ NULL_TREE, + srcsize, objsize); + + if (TREE_NO_WARNING (exp)) + /* If check_access issued a warning above, append the relevant + attribute to the string. */ + append_attrname (access, attrstr, sizeof attrstr); + } + + if (!*attrstr) + return; + + inform (DECL_SOURCE_LOCATION (fndecl), + "in a call to function %qD declared with attribute %qs", + fndecl, attrstr); + + /* Set the bit in case if was cleared and not set above. */ + TREE_NO_WARNING (exp) = true; +} + /* Fill in ARGS_SIZE and ARGS array based on the parameters found in CALL_EXPR EXP. @@ -1986,6 +2255,11 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED, /* Array for up to the two attribute alloc_size arguments. */ tree alloc_args[] = { NULL_TREE, NULL_TREE }; + /* Map of attribute read_only, write_only, or read_write specifications + for function arguments. */ + rdwr_map rdwr_idx; + init_attr_rdwr_indices (&rdwr_idx, fndecl); + /* I counts args in order (to be) pushed; ARGPOS counts in order written. */ for (argpos = 0; argpos < num_actuals; i--, argpos++) { @@ -2226,6 +2500,22 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED, alloc_args[0] = args[i].tree_value; else if (argpos == alloc_idx[1]) alloc_args[1] = args[i].tree_value; + + /* Save the actual argument that corresponds to the access attribute + operand for later processing. */ + if (attr_access *access = rdwr_idx.get (argpos)) + { + if (POINTER_TYPE_P (type)) + { + access->ptr = args[i].tree_value; + gcc_assert (access->size == NULL_TREE); + } + else + { + access->size = args[i].tree_value; + gcc_assert (access->ptr == NULL_TREE); + } + } } if (alloc_args[0]) @@ -2238,6 +2528,9 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED, /* Detect passing non-string arguments to functions expecting nul-terminated strings. */ maybe_warn_nonstring_arg (fndecl, exp); + + /* Check read_only, write_only, and read_write arguments. */ + maybe_warn_rdwr_sizes (&rdwr_idx, exp); } /* Update ARGS_SIZE to contain the total size for the argument block. diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi index 1c8ae0d5cd3..14218dbf53c 100644 --- a/gcc/doc/extend.texi +++ b/gcc/doc/extend.texi @@ -3850,6 +3850,104 @@ At present, a declaration to which @code{weakref} is attached can only be @code{static}. +@item read_only +@itemx read_only +@itemx read_only (@var{arg-index}) +@itemx read_only (@var{ref-index}, @var{size-index}) +@cindex @code{read_only} function attribute +The @code{read_only} attribute specifies that a function whose by-reference +arguments of @code{const}-qualified types denoted by @var{ref-index} (or +all such arguments if no @var{ref-index} is specified) are only used to +read from the objects referenced by the argument but not write to them. +Without the @var{size-index} operand, the pointer argument must be either +null or point to an array of at least one object of the pointed-to type. +Passing in the address of an uninitialized object is undefined. The same +pointer argument can appear as an operand to at most one @code{read_only}, +@code{write_only}, or @code{read_write_only} attribute. + +For example, the declaration below indicates that the @code{get_time} function +only reads the @code{struct tm} object pointed-to by the argument but doesn't +modify it. Casting away the constness and modifying the argument is undefined. +This information may be relied on to emit more efficient code at the call site, +or by the @option{-Wuninitilized} option to detect reads of uninitialized +variables. Built-in functions such as @code{strlen} declare their const +pointer arguments with the @code{read_only} attribute. + +@smallexample +time_t get_time (const struct tm *) __attribute__ ((read_only)); +@end smallexample + +The @var{size-index} argument optionally specifies the number of elements +of the array referenced by @var{ref-index} the function may read. For +references to objects of an incomplete type including @code{void}, the size +of the array element type is taken to be one. Passing in an array with +fewer elements is diagnosed by @option{-Wstringop-overflow}. +For example, the declaration below indicates that the function reads as many +elements of the array referenced by the first argument as specified by +the second argument. + +@smallexample +int sum (const int *, unsigned) __attribute__ ((read_only (1, 2))); +@end smallexample + + +@item read_write +@itemx read_write +@itemx read_write (@var{arg-index}) +@itemx read_write (@var{ref-index}, @var{size-index}) +@cindex @code{read_write} function attribute +The @code{read_write} attribute specifies that a function whose by-reference +arguments denoted by @var{ref-index} (or all such arguments if no +@var{ref-index} is specified) are used to both read from the objects +referenced by the argument and to write to them. Without the @var{size-index} +operand, the pointer argument must be either null or point to an array of +at least one object of the pointed-to type. Passing in the address of +an uninitialized object is undefined. The attribute is effectively +the union of the @code{read_only} and @code{write_only} attributes. +The same pointer argument can appear as an operand to at most one +@code{read_only}, @code{write_only}, or @code{read_write_only} attribute. + +@item write_only +@itemx write_only +@itemx write_only (@var{arg-index}) +@itemx write_only (@var{ref-index}, @var{size-index}) +@cindex @code{write_only} function attribute +The @code{write_only} attribute specifies that a function whose by-reference +arguments of non-@code{const}-qualified types denoted by @var{ref-index} (or +all such arguments if no @var{ref-index} is specified) are only used to write +into the objects referenced by the argument but not read from it. +Without the @var{size-index} operand, the pointer argument must be either +null or point to an array of sufficient size and alignment for at least +one object of the pointed-to type. The same pointer argument can appear +as an operand to at most one @code{read_only}, @code{write_only}, or +@code{read_write_only} attribute. + +For example, the declaration below indicates that the @code{init_tm} function +only writes to the @code{struct tm} object pointed-to by the argument but +does not read from it. This information may be relied on to emit more +efficient code at the call site, and diagnose initialized but otherwise +unused objects by one of the @option{-Wunused} options. Built-in functions +such as @code{memset} and @code{strcpy} declare their non-const pointer +argument with the @code{write_only} attribute. + +@smallexample +int init_tm (struct tm *) __attribute__ ((write_only)); +@end smallexample + +The @var{size-index} argument optionally specifies the number of elements +of the array referenced by @var{ref-index} the function may write. For +references to objects of an incomplete type including @code{void}, the size +of the array element type is taken to be one. Passing in an array with fewer +elements is diagnosed by @option{-Wstringop-overflow}. +For example, the declaration below indicates that the function writes as many +elements of the array referenced by the first argument as specified by +the second argument. + +@smallexample +void init (int *, unsigned, int) __attribute__ ((write_only (1, 2))); +@end smallexample + + @end table @c This is the end of the target-independent attribute table diff --git a/gcc/gimple.h b/gcc/gimple.h index 5a190b1714d..10223386d04 100644 --- a/gcc/gimple.h +++ b/gcc/gimple.h @@ -24,6 +24,8 @@ along with GCC; see the file COPYING3. If not see #include "tree-ssa-alias.h" #include "gimple-expr.h" +#include "function.h" +#include "basic-block.h" typedef gimple *gimple_seq_node; diff --git a/gcc/testsuite/c-c++-common/attr-nonstring-8.c b/gcc/testsuite/c-c++-common/attr-nonstring-8.c index 36ab2a66180..fbae8bae5f7 100644 --- a/gcc/testsuite/c-c++-common/attr-nonstring-8.c +++ b/gcc/testsuite/c-c++-common/attr-nonstring-8.c @@ -57,8 +57,8 @@ void test_strncat_nonstring_cst (char *d) T (strncat (nd3, ns3, 1)); T (strncat (nd3, ns3, 2)); T (strncat (nd3, ns3, 3)); /* { dg-warning "specified bound 3 equals destination size" } */ - T (strncat (nd3, ns3, 4)); /* { dg-warning "argument 2 declared attribute .nonstring. is smaller than the specified bound 4" } */ - /* { dg-warning "specified bound 4 exceeds destination size 3" "" { target *-*-* } .-1 } */ + /* Either of the two warnings below is fine. */ + T (strncat (nd3, ns3, 4)); /* { dg-warning "argument 2 declared attribute .nonstring. is smaller than the specified bound 4|specified bound 4 exceeds destination size 3" } */ T (strncat (d, pns, sizeof pns)); /* { dg-warning "argument to .sizeof. in .\[^\n\r\]*strncat\[^\n\r\]*. call is the same expression as the source" } */ } diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-22.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-22.c new file mode 100644 index 00000000000..183876a1c12 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-22.c @@ -0,0 +1,175 @@ +/* PR middle-end/83859 - attribute to establish relation between parameters + for buffer and its size + Test to verify that -Wstringop-overflow warnings are issued even with + no optimization for calls to user-defined functions with attributes + read_only and write_only and with constant out-of-bounds arguments. + { dg-do compile } + { dg-options "-O0 -Wall" } */ + +#define INT_MAX __INT_MAX__ +#define INT_MIN (-INT_MAX - 1) + +#define RDONLY(...) __attribute__ ((read_only (__VA_ARGS__))) +#define WRONLY(...) __attribute__ ((write_only (__VA_ARGS__))) +#define RDWR(...) __attribute__ ((read_write (__VA_ARGS__))) + +extern const char s1[1], s2[2], s3[3]; +extern char d1[1], d2[2], d3[3]; + +/* Exercise that null pointers are allowed in functions declared with + the attribute without any operands. */ + +RDONLY () void +rd1_int (const int*); // { dg-message "in a call to function 'rd1_int' declared with attribute 'read_only \\\(1\\\)'" } + +void test_rd1_int (void) +{ + rd1_int (0); + + int i = 0; + rd1_int (&i); + + rd1_int ((int*)s1); // { dg-warning "reading 4 bytes from a region of size 1" } +} + +/* Exercise null pointer detection in functions declared with + the attribute and with non-zero size. */ + +RDONLY (2, 1) void +rd2_1 (int, const void*); // { dg-message "in a call to function 'rd2_1' declared with attribute 'read_only \\\(2, 1\\\)" } + +void test_rd2_1 (void) +{ + rd2_1 (0, 0); + rd2_1 (1, ""); + rd2_1 (1, 0); // { dg-warning "argument 2 is null but the corresponding size argument 1 value is 1" } +} + +WRONLY (3, 1) void +wr3_1 (int, int, void*); // { dg-message "in a call to function 'wr3_1' declared with attribute 'write_only \\\(3, 1\\\)" } + +void test_wr3_1 (void) +{ + wr3_1 (0, 0, 0); + wr3_1 (1, 0, d1); + wr3_1 (2, 1, 0); // { dg-warning "argument 3 is null but the corresponding size argument 1 value is 2" } +} + + +/* Exercise pointer to an incomplete type other than void. */ + +struct Incomplete; +extern struct Incomplete inc; + +RDONLY () void +rd_inc (const struct Incomplete*); + +void test_rd_inc (const struct Incomplete *pinc) +{ + rd_inc (0); + + rd_inc (pinc); + rd_inc ((const struct Incomplete*)s1); + + rd_inc ((const struct Incomplete*)&s1[1]); + // { dg-warning "'rd_inc' reading 1 byte from a region of size 0" "past-the-end pointer" { target *-*-* } .-1 } +} + +RDONLY (1, 2) void +rd1_2_inc (const struct Incomplete*, unsigned); + +void test_rd1_2_inc (const struct Incomplete *pinc) +{ + rd1_2_inc (0, 0); + rd1_2_inc (0, 1); // { dg-warning "argument 1 is null but the corresponding size argument 2 value is 1" } + + rd1_2_inc (pinc, 1); + rd1_2_inc (&inc, 1); + + rd1_2_inc (pinc, 123); + rd1_2_inc (&inc, 456); + + rd1_2_inc ((const struct Incomplete*)s3, 4); + // { dg-warning "'rd1_2_inc' reading 4 bytes from a region of size 3" "small buffer cast to incomplete" { target *-*-* } .-1 } +} + + +/* Verify the handling of two attributes sharing the same size operand . */ + +RDONLY (1, 3) WRONLY (2, 3) void +rd1_3_wr2_3 (const void*, void*, int); + +void test_rd1_3_wr2_3 (void) +{ + rd1_3_wr2_3 (s1, d1, 0); + rd1_3_wr2_3 (s1, d1, 1); + + rd1_3_wr2_3 (s1, d1, 2); + // { dg-warning "'rd1_3_wr2_3' reading 2 bytes from a region of size 1" "read" { target *-*-* } .-1 } + // { dg-warning "'rd1_3_wr2_3' writing 2 bytes into a region of size 1" "write" { target *-*-* } .-2 } + + rd1_3_wr2_3 (s1, d2, 2); + // { dg-warning "'rd1_3_wr2_3' reading 2 bytes from a region of size 1" "read" { target *-*-* } .-1 } + + rd1_3_wr2_3 (s2, d1, 2); + // { dg-warning "'rd1_3_wr2_3' writing 2 bytes into a region of size 1" "write" { target *-*-* } .-1 } +} + + +/* Verify the handling of multiple attributes of the same kind with + out-of-order operands. */ + +RDONLY (1, 6) RDONLY (2, 5) RDONLY (3, 4) void +rd1_6_2_5_3_4 (const void *s1, const void *s2, const void *s3, + int n3, int n2, int n1); + +void test_rd1_6_2_5_3_4 (void) +{ + rd1_6_2_5_3_4 (s1, s2, s3, 4, 2, 1); // { dg-warning "reading 4 bytes from a region of size 3" } + rd1_6_2_5_3_4 (s1, s2, s3, 3, 5, 1); // { dg-warning "reading 5 bytes from a region of size 2" } + rd1_6_2_5_3_4 (s1, s2, s3, 3, 2, 6); // { dg-warning "reading 6 bytes from a region of size 1" } +} + + +/* Verify the handling of multiple attributes of different kinds with + out-of-order operands. */ + +RDONLY (1, 6) WRONLY (2, 5) RDONLY (3, 4) void +rd1_6_wr2_5_rd3_4 (const void *s1, void *d2, const void *s3, + int n3, int n2, int n1); + +void test_rd1_6_wr2_5_rd3_4 (void) +{ + rd1_6_wr2_5_rd3_4 (s1, d2, s3, 7, 2, 1); // { dg-warning "reading 7 bytes from a region of size 3" } + rd1_6_wr2_5_rd3_4 (s1, d2, s3, 3, 8, 1); // { dg-warning "writing 8 bytes into a region of size 2" } + rd1_6_wr2_5_rd3_4 (s1, d2, s3, 3, 2, 9); // { dg-warning "reading 9 bytes from a region of size 1" } +} + + +RDONLY (6, 1) WRONLY (5, 2) RDWR (4, 3) void +rd6_1_wr5_2_rd4_3 (int n1, int n2, int n3, + void *d3, void *d2, const void *s1); + +void test_rd6_1_wr5_2_rd4_3 (void) +{ + rd6_1_wr5_2_rd4_3 (7, 2, 1, d1, d2, s3); // { dg-warning "reading 7 bytes from a region of size 3" } + rd6_1_wr5_2_rd4_3 (3, 8, 1, d1, d2, s3); // { dg-warning "writing 8 bytes into a region of size 2" } + rd6_1_wr5_2_rd4_3 (3, 2, 9, d1, d2, s3); // { dg-warning "writing 9 bytes into a region of size 1" } +} + + +RDONLY (1, 3) WRONLY (2, 4) void +rd1_3_wr2_4 (const void*, void*, int, int); + +void test_rd1_3_wr2_4 (const void *s, void *d, int n1, int n2) +{ + rd1_3_wr2_4 (s, d, 1, 2); + rd1_3_wr2_4 (s, d, 123, 456); + rd1_3_wr2_4 (s, d, INT_MAX, INT_MAX); + rd1_3_wr2_4 (s, d, -1, 2); // { dg-warning "argument 3 value -1 is negative" } + + const char s11[11] = "0123456789"; + + rd1_3_wr2_4 (s11, d, 11, n2); + rd1_3_wr2_4 (s11, d, 12, n2); // { dg-warning "'rd1_3_wr2_4' reading 12 bytes from a region of size 11" } +} diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-23.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-23.c new file mode 100644 index 00000000000..e104d81b040 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-23.c @@ -0,0 +1,153 @@ +/* PR middle-end/83859 - attribute to establish relation between parameters + for buffer and its size + Test to verify that with optimization enabled, -Wstringop-overflow + warnings are issued for calls to user-defined functions with attributes + read_only and write_only and with non-constant out-of-bounds arguments. + { dg-do compile } + { dg-options "-O2 -Wall" } */ + +#include "range.h" + +#define INT_MAX __INT_MAX__ +#define INT_MIN (-INT_MAX - 1) + +#define RDONLY(...) __attribute__ ((read_only (__VA_ARGS__))) +#define WRONLY(...) __attribute__ ((write_only (__VA_ARGS__))) +#define RDWR(...) __attribute__ ((read_write (__VA_ARGS__))) + +/* Exercise null pointer detection. */ + +RDONLY (2, 1) void +rd2_1 (int, const void*); // { dg-message "in a call to function 'rd2_1' declared with attribute 'read_only \\\(2, 1\\\)" } + +void test_rd2_1 (void) +{ + { + void *null = 0; + void *p = &null; + + rd2_1 (0, null); + rd2_1 (1, p); + } + + { + void *null = 0; + rd2_1 (1, null); // { dg-warning "argument 2 is null but the corresponding size argument 1 value is 1" } + } + + { + void *null = 0; + rd2_1 (SR (1, 2), null); // { dg-warning "argument 2 is null but the corresponding size argument 1 range is \\\[1, 2]" } + } +} + +WRONLY (3, 1) void +wr3_1 (int, int, void*); // { dg-message "in a call to function 'wr3_1' declared with attribute 'write_only \\\(3, 1\\\)" } + +void test_wr3_1 (void) +{ + { + void *null = 0; + void *p = &null; + + wr3_1 (SR (0, 1), 0, null); + wr3_1 (SR (1, 1), 0, p); + } + + void *null = 0; + + wr3_1 (SR (1, 2), 1, null); // { dg-warning "argument 3 is null but the corresponding size argument 1 range is \\\[1, 2]" } +} + + +WRONLY (2, 1) void +wr2_1 (int, void*); + +void test_wrd2_1 (int n) +{ + wr2_1 (0, 0); + wr2_1 (SR (-1, 1), 0); + wr2_1 (SR (0, 1), 0); + wr2_1 (SR (1, 2), 0); // { dg-warning "argument 2 is null but the corresponding size argument 1 range is \\\[1, 2]" } + + /* This should probably be diagnosed but to avoid false positives + caused by jump threading and such it would have to be done + earlier than it is now. */ + wr2_1 (n, 0); // { dg-warning "argument 2 is null" "unimplemented" { xfail *-*-* } } +} + + +/* Exercise pointer to an incomplete type other than void. */ + +struct Incomplete; +extern struct Incomplete inc; + +extern char ax[]; + +WRONLY (1, 2) void +wr1_2_inc (struct Incomplete*, unsigned); + +void test_wr1_2_inc (struct Incomplete *pinc, unsigned n) +{ + wr1_2_inc (0, 0); + wr1_2_inc (0, 1); // { dg-warning "argument 1 is null but the corresponding size argument 2 value is 1" } + + wr1_2_inc (pinc, 1); + wr1_2_inc (&inc, 1); + + wr1_2_inc (pinc, 123); + wr1_2_inc (&inc, 456); + + char a3[3]; + pinc = (struct Incomplete*)a3; + wr1_2_inc (pinc, SR (3, 4)); + wr1_2_inc (pinc, SR (4, 5)); + // { dg-warning "'wr1_2_inc' writing between 4 and 5 bytes into a region of size 3" "small buffer cast to incomplete" { target *-*-* } .-1 } + + pinc = (struct Incomplete*)ax; + wr1_2_inc (pinc, SR (123, 456)); + + char vla[n]; + pinc = (struct Incomplete*)vla; + wr1_2_inc (pinc, SR (345, 456)); +} + + +RDONLY (1, 3) WRONLY (2, 4) void +rd1_3_wr2_4 (const void*, void*, int, int); + +void test_rd1_3_wr2_4 (const void *s, void *d, int n1, int n2) +{ + rd1_3_wr2_4 (s, d, 1, 2); + rd1_3_wr2_4 (s, d, 123, 456); + rd1_3_wr2_4 (s, d, INT_MAX, INT_MAX); + rd1_3_wr2_4 (s, d, -1, 2); // { dg-warning "argument 3 value -1 is negative" } + + const int ir_min_m1 = SR (INT_MIN, -1); + rd1_3_wr2_4 (s, d, ir_min_m1, 2); // { dg-warning "argument 3 range \\\[-\[0-9\]+, -1] is negative" } + + rd1_3_wr2_4 (s, d, SR (-1, 0), 2); + rd1_3_wr2_4 (s, d, SR (INT_MIN, INT_MAX), 2); + + rd1_3_wr2_4 (s, d, n1, n2); + + + const char s11[11] = "0123456789"; + + rd1_3_wr2_4 (s11, d, 11, n2); + rd1_3_wr2_4 (s11, d, 12, n2); // { dg-warning "'rd1_3_wr2_4' reading 12 bytes from a region of size 11" } + + rd1_3_wr2_4 (s11, d, SR (0, 11), n2); + rd1_3_wr2_4 (s11, d, SR (0, 12), n2); + rd1_3_wr2_4 (s11, d, SR (11, 12), n2); + rd1_3_wr2_4 (s11, d, SR (11, INT_MAX), n2); + rd1_3_wr2_4 (s11, d, SR (12, 13), n2); // { dg-warning "'rd1_3_wr2_4' reading between 12 and 13 bytes from a region of size 11" } + + char d4[4]; + rd1_3_wr2_4 (s, d4, n1, 4); + rd1_3_wr2_4 (s, d4, n1, 5); // { dg-warning "'rd1_3_wr2_4' writing 5 bytes into a region of size 4" } + + rd1_3_wr2_4 (s11, d4, SR (12, 13), SR (5, 6)); + // { dg-warning "'rd1_3_wr2_4' reading between 12 and 13 bytes from a region of size 11" "read" { target *-*-* } .-1 } + // { dg-warning "'rd1_3_wr2_4' writing between 5 and 6 bytes into a region of size 4" "read" { target *-*-* } .-2 } +} diff --git a/gcc/testsuite/gcc.dg/attr-read-only.c b/gcc/testsuite/gcc.dg/attr-read-only.c new file mode 100644 index 00000000000..5f55b8b3a7c --- /dev/null +++ b/gcc/testsuite/gcc.dg/attr-read-only.c @@ -0,0 +1,65 @@ +/* PR middle-end/83859 - attribute to establish relation between parameters + for buffer and its size + Test to verify the handling of attribute read_only syntax. + { dg-do compile } + { dg-options "-Wall -ftrack-macro-expansion=0" } */ + +#define RDONLY(...) __attribute__ ((read_only (__VA_ARGS__))) + +int __attribute__ ((read_only)) +rdonly_v_all (void); /* { dg-error "attribute .read_only. on a function with no pointer arguments" } */ + +int __attribute__ ((read_only)) +rdonly_i_all (int); /* { dg-error "attribute .read_only. on a function with no pointer arguments" } */ + +int __attribute__ ((read_only)) +rdonly_pi_all (const int*); + +int __attribute__ ((read_only)) +rdonly_pv_pi_all (const void*, const int*); + +int __attribute__ ((read_only)) +rdonly_i_pc_i_pd_all (int, const char*, int, const double*); + +int RDONLY (1) +rdonly_pcv_1 (const void*); +int RDONLY (2) +rdonly_i_pcv_2 (int, const void*); +int RDONLY (3) +rdonly_i_i_pcv_3 (int, int, const void*); + +int RDONLY (0 + 1) +rdonly_pcv_0p1 (const void*); + +int RDONLY (2 - 1) +rdonly_pcv_2m1 (const void*); + +int RDONLY (1, 1) +rdonly_pv_pi_1_1 (const void*, const int*); /* { dg-error "attribute .read_only\\(1, 1\\). second operand references non-integer argument type .const void *." } */ + +int RDONLY (1, 2) +rdonly_pcv_pc_1_2 (const void*, char*); /* { dg-error "attribute .read_only\\(1, 2\\). second operand references non-integer argument type .char *." } */ + +int RDONLY (2, 1) +rdonly_pcd_pcv_2_1 (const double*, const void*); /* { dg-error "attribute .read_only\\(2, 1\\). second operand references non-integer argument type .const double *." } */ + +int __attribute__ ((read_only)) +rdonly_pcv_pcv_all (const void*, const void*); + +int RDONLY (2, 2) +rdonly_pi_pcv_2_2 (int*, const void*); /* { dg-error "second operand references non-integer argument type .const void *." } */ + +int RDONLY (4) +rdonly_i_i_i_4 (int, int, int); /* { dg-error "attribute .read_only\\(4\\). operand exceeds number of arguments 3" } */ + +int RDONLY (1) +rdonly_i_1 (int); /* { dg-error "attribute .read_only\\(1\\). references non-pointer argument type .int." } */ + +int RDONLY (2) +rdonly_i_pc (int, char*); /* { dg-warning "attribute .read_only\\(2\\). operand references non-.const. argument type .char \\\*." } */ + +int RDONLY (-1) +rdonly_pcv_m1 (const void*); /* { dg-error "attribute .read_only\\(-1\\). invalid operand" } */ + +int RDONLY ("blah") +rdonly_pcv_str (const void*); /* { dg-error "attribute .read_only\\(\"blah\"\\). invalid operand" } */ diff --git a/gcc/testsuite/gcc.dg/attr-write-only.c b/gcc/testsuite/gcc.dg/attr-write-only.c new file mode 100644 index 00000000000..27db9c17ce7 --- /dev/null +++ b/gcc/testsuite/gcc.dg/attr-write-only.c @@ -0,0 +1,51 @@ +/* Test to verify the handling of attribute write_only syntax. + { dg-do compile } + { dg-options "-Wall -ftrack-macro-expansion=0" } */ + +#define WRONLY(...) __attribute__ ((write_only (__VA_ARGS__))) + +void __attribute__ ((write_only)) +wronly_v_all (void); /* { dg-error "attribute .write_only. on a function with no pointer arguments" } */ + +void __attribute__ ((write_only)) +wronly_i_all (int); /* { dg-error "attribute .write_only. on a function with no pointer arguments" } */ + +void wronly_v_1 (void*) WRONLY (1); +void wronly_iv_2 (int, void*) WRONLY (2); +void wronly_iiv_3 (int, int, void*) WRONLY (3); + +void wronly_v_0p1 (void*) WRONLY (0 + 1); +void wronly_v_2m1 (void*) WRONLY (2 - 1); + +void WRONLY (1, 1) +wronly_v_1_1 (void*); /* { dg-error "attribute .write_only\\(1, 1\\). second operand references non-integer argument type .void *." } */ + +void WRONLY (1, 2) +wronly_vv_1_2 (void*, void*); /* { dg-error "attribute .write_only\\(1, 2\\). second operand references non-integer argument type .void *." } */ + +void WRONLY (2, 1) +wronly_vv_2_1 (void*, void*); /* { dg-error "attribute .write_only\\(2, 1\\). second operand references non-integer argument type .void *." } */ + +void __attribute__ ((write_only)) +wronly_vv_all (void*, void*); + +void WRONLY (1, 1) +wronly_vv_1_1 (void*, void*); /* { dg-error "attribute .write_only\\(1, 1\\). second operand references non-integer argument type .void *." } */ + +void WRONLY (2, 2) +wronly_vv_1_1 (void*, void*); /* { dg-error "second operand references non-integer argument type .void *." } */ + +void WRONLY (4) +wronly_4_exceed (int, int, int); /* { dg-error "attribute .write_only\\(4\\). operand exceeds number of arguments 3" } */ + +void WRONLY (1) +wronly_1_i (int); /* { dg-error "attribute .write_only\\(1\\). references non-pointer argument type .int." } */ + +void WRONLY (2) +wronly_2_cst (int, const char*); /* { dg-error "attribute .write_only\\(2\\). operand references .const.-qualified argument type .const char *." } */ + +void WRONLY (-1) +wronly_m1_i (void*); /* { dg-error "attribute .write_only\\(-1\\). invalid operand" } */ + +void WRONLY ("blah") +wronly_str_i (void*); /* { dg-error "attribute .write_only\\(\"blah\"\\). invalid operand" } */