From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 21340 invoked by alias); 15 Nov 2019 21:28:26 -0000 Mailing-List: contact gcc-patches-help@gcc.gnu.org; run by ezmlm Precedence: bulk List-Id: List-Archive: List-Post: List-Help: Sender: gcc-patches-owner@gcc.gnu.org Received: (qmail 21327 invoked by uid 89); 15 Nov 2019 21:28:26 -0000 Authentication-Results: sourceware.org; auth=none X-Spam-SWARE-Status: No, score=-18.6 required=5.0 tests=AWL,BAYES_00,FREEMAIL_FROM,GIT_PATCH_0,GIT_PATCH_1,GIT_PATCH_2,GIT_PATCH_3,RCVD_IN_DNSWL_NONE,SPF_PASS,UNSUBSCRIBE_BODY autolearn=ham version=3.3.1 spammy=specifications, need, associates, fe X-HELO: mail-pf1-f195.google.com Received: from mail-pf1-f195.google.com (HELO mail-pf1-f195.google.com) (209.85.210.195) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with ESMTP; Fri, 15 Nov 2019 21:28:16 +0000 Received: by mail-pf1-f195.google.com with SMTP id q26so7247911pfn.11 for ; Fri, 15 Nov 2019 13:28:15 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:subject:to:references:message-id:date:user-agent:mime-version :in-reply-to:content-language; bh=tfVJQHTlyCPuMhJ/YOR6qhFedf5k6gCR29i18Pb9DlQ=; b=P3pOrtSt+B4Dprprz/rzDY90vCj+rWXtVgXEiuRz1LgJNctVd/FYOYLWTGdZWuz+EQ rBJy/kyud2qcHQPyTWSqDTVxigzj36uwo2D9gsluZW2mr1Ffql94DlvZG+00d5+VL5q8 hfhcHI02CC4+sgVcmM8ziCCOAX6WZaJaUpTt6tZbKZXSNtQyiNHAfjPQFaIDbgKs//lb 5nu5cmS2sUwIgcSzTFhVoV2g70bSg9wqYlShZgoSM2qXJEEhMU8NMmmotVQIQfx2TJQf 5h79CW65xtKH+w/vLQi6Xy7JvPvca7FLxjUlqgDi2LHZzMljZSJbMlfJbrvrOjT1p4c7 SW1g== Return-Path: Received: from [192.168.0.41] (97-118-98-145.hlrn.qwest.net. [97.118.98.145]) by smtp.gmail.com with ESMTPSA id r16sm9072808pgl.77.2019.11.15.13.28.11 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Fri, 15 Nov 2019 13:28:11 -0800 (PST) From: Martin Sebor Subject: Re: [WIP PATCH] add object access attributes (PR 83859) To: Jeff Law , gcc-patches References: <056e2b5b-696c-ca69-9027-7d2369354b07@gmail.com> Message-ID: <3b148654-b12c-1e7c-32d2-78df9d6c70e7@gmail.com> Date: Fri, 15 Nov 2019 21:41:00 -0000 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Thunderbird/60.6.1 MIME-Version: 1.0 In-Reply-To: Content-Type: multipart/mixed; boundary="------------62167FBF21A3BF22B2E1A0E6" X-IsSubscribed: yes X-SW-Source: 2019-11/txt/msg01474.txt.bz2 This is a multi-part message in MIME format. --------------62167FBF21A3BF22B2E1A0E6 Content-Type: text/plain; charset=utf-8; format=flowed Content-Transfer-Encoding: 8bit Content-length: 14893 On 10/27/19 11:31 AM, Jeff Law wrote: > On 9/29/19 1:51 PM, Martin Sebor wrote: >> -Wstringop-overflow detects a subset of past-the-end read and write >> accesses by built-in functions such as memcpy and strcpy.  It relies >> on the functions' effects the knowledge of which is hardwired into >> GCC.  Although it's possible for users to create wrappers for their >> own functions to detect similar problems, it's quite cumbersome and >> so only lightly used outside system libraries like Glibc.  Even Glibc >> only checks for buffer overflow and not for reading past the end. >> >> PR 83859 asks to expose the same checking that GCC does natively for >> built-in calls via a function attribute that associates a pointer >> argument with the size argument, such as: >> >>   __attribute__((buffer_size (1, 2))) void >>   f (char* dst, size_t dstsize); >> >> The attached patch is my initial stab at providing this feature by >> introducing three new attributes: >> >>   * read_only (ptr-argno, size-argno) >>   * read_only (ptr-argno, size-argno) >>   * read_write (ptr-argno, size-argno) >> >> As requested, the attributes associate a pointer parameter to >> a function with a size parameter.  In addition, they also specify >> how the function accesses the object the pointer points to: either >> it only reads from it, or it only writes to it, or it does both. >> >> Besides enabling the same buffer overflow detection as for built-in >> string functions they also let GCC issue -Wuninitialized warnings >> for uninitialized objects passed to read-only functions by reference, >> and -Wunused-but-set warnings for objects passed to write-only >> functions that are otherwise unused (PR 80806).  The -Wununitialized >> part is done. The -Wunused-but-set detection is implemented only in >> the C FE and not yet in C++. >> >> Besides the diagnostic improvements above the attributes also open >> up optimization opportunities such as DCE.  I'm still working on this >> and so it's not yet part of the initial patch. >> >> I plan to finish the patch for GCC 10 but I don't expect to have >> the time to start taking advantage of the attributes for optimization >> until GCC 11. >> >> Besides regression testing on x86_64-linux, I also tested the patch >> by compiling Binutils/GDB, Glibc, and the Linux kernel with it.  It >> found no new problems but caused a handful of -Wunused-but-set-variable >> false positives due to an outstanding bug in the C front-end introduced >> by the patch that I still need to fix. >> >> Martin >> >> gcc-80806.diff >> >> PR c/80806 - gcc does not warn if local array is memset only >> PR middle-end/83859 - attribute to associate buffer and its size >> >> gcc/ChangeLog: >> >> PR c/80806 >> PR middle-end/83859 >> * builtin-attrs.def (ATTR_NO_SIDE_EFFECT): New. >> (ATTR_READ_ONLY, 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 (no_side_effect): Document new attribute. >> (read_only, write_only, read_write): Same. >> * tree-ssa-uninit.c (maybe_warn_uninit_accesss): New functions. >> (warn_uninitialized_vars): Rename argument. Factor out code into >> maybe_warn_uninit_accesss. Call it. >> >> gcc/c/ChangeLog: >> >> PR c/80806 >> PR middle-end/83859 >> * c-parser.c (c_parser::no_set_read, no_init, in_arg): New members. >> (c_parser_expr_list): Add argument. >> (c_parser_attribute): Pass a new argument to get_nonnull_operand. >> (c_parser_initializer): Set parser->in_init. >> (c_parser_binary_expression): Use parser->no_set_read. >> (c_parser_unary_expression): Same. >> (c_parser_sizeof_expression): Use parser->in_arg. >> (c_parser_postfix_expression_after_primary): Adjust. >> (is_write_only_p): New function. >> (c_parser_expr_list): Add argument. >> Avoid setting DECL_READ_P for decls passed to write-only function >> arguments. >> (c_parser_objc_keywordexpr): Pass a new argument to c_parser_expr_list. >> (c_parser_oacc_wait_list): Same. >> * c-tree.h (parser_build_binary_op): Add argument. >> * c-typeck.c (default_conversion): Same. Use it. >> (parser_build_binary_op): Same. >> (build_binary_op): Same. >> >> gcc/c-family/ChangeLog: >> >> PR c/80806 >> PR middle-end/83859 >> * c-attribs.c (handle_no_side_effect_attribute): New function. >> (handle_read_only_attribute, 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/cp/ChangeLog: >> >> PR c/80806 >> PR middle-end/83859 >> * typeck.c (cp_default_conversion): Add argument. >> (build_binary_op): Same. >> >> gcc/testsuite/ChangeLog: >> >> PR c/80806 >> PR middle-end/83859 >> * c-c++-common/Wsizeof-pointer-memaccess1.c: Adjust. >> * c-c++-common/Wsizeof-pointer-memaccess2.c: Adjust. >> * gcc.dg/Wstrict-aliasing-bogus-vla-1.c: Adjust. >> * gcc/testsuite/gcc.dg/Wunused-but-set-var.c: New test. >> * gcc.dg/attr-alloc_size.c: Adjust. >> * gcc/testsuite/gcc.dg/attr-read-only-2.c: New test. >> * gcc/testsuite/gcc.dg/attr-read-only.c: New test. >> * gcc/testsuite/gcc.dg/attr-write-only-2.c: New test. >> * gcc/testsuite/gcc.dg/attr-write-only.c: New test. >> * gcc.dg/nonnull-3.c: Adjust. >> * gcc.dg/pr40340-2.c: Adjust. >> * gcc.dg/pr78768.c: Adjust. >> * gcc.dg/pr79715.c: Adjust. >> * gcc.dg/tree-ssa/builtin-snprintf-7.c: Adjust. >> * gcc/testsuite/gcc.dg/uninit-builtin.c: New test. > You mention that this is almost done, but not yet finished. It was not finished in the sense that I knew of a false positive warning due to the C front-end change. (I was also looking for feedback on some of the design and implementation choices.) If I were > working on this I could have split this up into adding the support for > the attributes first without trying to use them anywhere first. Then a > separate patch that adds the attribute to the builtins, then a patch > that exploited the new attributes to do something useful. > > Please try to avoid creating large patches that are so easily broken up. > It just makes review harder than it needs to be and slows down getting > the patches integrated. I couldn't think of a way to test the new attributes without also making other changes, but I've come up with one so I extracted just the basic bits from it and I'm submitting those now. I'll post the rest as I wrap them up. > Your ChangeLog references "no_side_effect" in a few places. But I don't > see any of those changes actually in the patch, except for adding it in > builtin-attrs.def. I'm guessing those are supposed to all be part of a > follow-up patch? Yes, no_side_effect is part of the optimization changes. I have removed mentions of it in the attached revision. > > FWIW, I believe there may be a DSE case that your patches allow us to > handle. Essentially we had a case where DSE didn't fire because we > didn't have detailed information about how the operands to one of the > mem* or str* calls was used. I don't expect you to own this missing opt > as part of this submission. That's right. This is something I'd like to tackle for GCC 11. > > >> diff --git a/gcc/attribs.h b/gcc/attribs.h >> index 23a7321e04a..0e2320701b1 100644 >> --- a/gcc/attribs.h >> +++ b/gcc/attribs.h >> @@ -218,4 +218,36 @@ 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 relevant arguments are of >> + the given kind, 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 Kind { read_only, write_only, read_write }; > Nit: Mixed case "Kind" use either KIND or kind. FWIW: I find either alternative makes the code harder, not easier to read, and neither would make the code fully conform to the GNU style (it asks to capitalize enum constants). I've changed it to "kind" (though I think it's fine either way and wish we would dwell less on these things). >> diff --git a/gcc/c/c-typeck.c b/gcc/c/c-typeck.c >> index d4e12eb93d1..2e439c43b91 100644 >> --- a/gcc/c/c-typeck.c >> +++ b/gcc/c/c-typeck.c >> @@ -2146,14 +2146,30 @@ perform_integral_promotions (tree exp) >> In addition, manifest constants symbols are replaced by their values. */ >> >> tree >> -default_conversion (tree exp) >> +default_conversion (tree exp, bool read_p /* = true */) > Please update the function comment to describe the new argument. I > think you need to do that for c_parser_expr_list and > parser_build_binary_op and build_binary_op. > > >> diff --git a/gcc/gimple.h b/gcc/gimple.h >> index cf1f8da5ae2..4d850a088cf 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" > How important is this (ie, how painful is it to have the .c/.cc files > include function.h/basic-block.h? We generally frown on having one > header include others like this. The header already has a few #include directives presumably to allow the inline functions compile. I get compilation errors without the #include directives and haven't tried to solve them differently. I know you agree but I can't help but this as an opportunity to vent: it's really a horrible practice not to have headers stand on their own. It causes so much frustration whenever a header is included in a .c file that doesn't already include all the prerequisite headers. There's got to be a tool out there that would fix this mess automatically for us. >> diff --git a/gcc/tree-ssa-uninit.c b/gcc/tree-ssa-uninit.c >> index fe8f8f0bc28..b9d455159cb 100644 >> --- a/gcc/tree-ssa-uninit.c >> +++ b/gcc/tree-ssa-uninit.c > Something else to consider (as a separate follow-up). > > IIUC your code adds checking arguments at call sites which is a nice > improvement. There may be BZs related to this issue. > > Another concept we might want to consider based on what I've seen pop up > fairly often in code is the concept of "must write". In the caller we'd > consider passing a must-write object to a function call as > initialization which would cut down on false positives. We could verify > the behavior in the callee. This is pretty much what the write_only attribute does. For the purposes of -Wuninitialized, at the call site, the compiler assumes that the object has been written to. (I've removed this from the revised patch.) There is nothing to enforce that the function actually does write into the object. That could be added when the -Wuninitialized bits are added back, or later on. > > Also, I think Fortran has the concept of "Intent" which does largely the > same thing you're doing. You might consider reaching out to the Fortran > front-end folks and see if they can encode their Intent information into > your attributes. I believe there's BZs related to using Intent > information to avoid false positives from the uninit warning pass. Thanks for the suggestion. I will do that for GCC 11. I take Richard's point that the attributes' semantics need to be clearly and carefully specified before they're put to use for optimization. > > I don't see anything terribly concerning. Looking forward to the final > iteration here. Attached is a subset of the original patch that just adds the three attributes and uses them to do buffer overflow checking. I have also enhanced the detection of invalid arguments (null pointers, negative sizes). Retested on x86_64-linux. Martin --------------62167FBF21A3BF22B2E1A0E6 Content-Type: text/x-patch; name="gcc-83859.diff" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="gcc-83859.diff" Content-length: 70737 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" } */ --------------62167FBF21A3BF22B2E1A0E6--