From patchwork Sat Dec 7 00:19:36 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Martin Sebor X-Patchwork-Id: 1205324 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=gcc.gnu.org (client-ip=209.132.180.131; helo=sourceware.org; envelope-from=gcc-patches-return-515399-incoming=patchwork.ozlabs.org@gcc.gnu.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=gcc.gnu.org header.i=@gcc.gnu.org header.b="O2o/2uWF"; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="CDUbdF/I"; dkim-atps=neutral Received: from sourceware.org (server1.sourceware.org [209.132.180.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 47V98747WCz9sPh for ; Sat, 7 Dec 2019 11:20:07 +1100 (AEDT) DomainKey-Signature: a=rsa-sha1; c=nofws; d=gcc.gnu.org; h=list-id :list-unsubscribe:list-archive:list-post:list-help:sender :subject:from:to:references:message-id:date:mime-version :in-reply-to:content-type; q=dns; s=default; b=NBB5phriPWOjtWaNj VYCBEwoQE85eGv2IaO/LZbES39wVl+OKmTx3p9I1+5xJaFdTDWngGLkAPhDILiaC 8w7ZIp0WxWwfdMbRvn0w4liy2EJkKPEvTsXOfKdh+PvEMkSf48E1Yb29vdeuRv4H h9dcM4R+DlMFSmbNcf+tXPfmYs= DKIM-Signature: v=1; a=rsa-sha1; c=relaxed; d=gcc.gnu.org; h=list-id :list-unsubscribe:list-archive:list-post:list-help:sender :subject:from:to:references:message-id:date:mime-version :in-reply-to:content-type; s=default; bh=bHDZeXJ6v6QrlcLY72QCAom LZ64=; b=O2o/2uWFnCfUuWxRpVJ8v+uUO1kFxl/qYazdl2hjDK0E7KCyiKFbL6T G4xELW7y4ZU5Fo4V4vUGF1JZDQ7xpVzpPobsceMMkWD1oNleVwwS4J2DiEV2Pn9A HIEsnIEtY+iS6x/onMp3R0rvIQhlmWRh0bEjkOKRmGKj8o18qTk8= Received: (qmail 77778 invoked by alias); 7 Dec 2019 00:19:53 -0000 Mailing-List: contact gcc-patches-help@gcc.gnu.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Archive: List-Post: List-Help: Sender: gcc-patches-owner@gcc.gnu.org Delivered-To: mailing list gcc-patches@gcc.gnu.org Received: (qmail 77769 invoked by uid 89); 7 Dec 2019 00:19:52 -0000 Authentication-Results: sourceware.org; auth=none X-Spam-SWARE-Status: No, score=-19.7 required=5.0 tests=AWL, BAYES_00, FREEMAIL_FROM, GIT_PATCH_0, GIT_PATCH_1, GIT_PATCH_2, GIT_PATCH_3, KAM_SHORT, RCVD_IN_DNSWL_NONE, SPF_PASS autolearn=ham version=3.3.1 spammy=oracle X-HELO: mail-pf1-f196.google.com Received: from mail-pf1-f196.google.com (HELO mail-pf1-f196.google.com) (209.85.210.196) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with ESMTP; Sat, 07 Dec 2019 00:19:40 +0000 Received: by mail-pf1-f196.google.com with SMTP id d199so4167509pfd.11 for ; Fri, 06 Dec 2019 16:19:40 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=subject:from:to:references:message-id:date:user-agent:mime-version :in-reply-to:content-language; bh=cQhJNYY0eDGV6WS98ln/5G4Muol3lJnJGAWav5PH4/I=; b=CDUbdF/IUleiMo/D1D6sqMOre3TV/mdQzxN2yNCh8PWEMwwIe9iQKWo33koKXG4zWi hkuckbnmYNXi3mTNtOUZgVEeAWh532y5eCjSZkf/v8oR38UEOwIpkAY3Swf70IBglEEf IfaLL9Cf5P/tN+IefejjXLBnLzygAMNSgXkddqE6uvHq5bREABTQ0loElJa2aEL+0CFa uoQ3kEUlcmRtTDMKi8uPQwxmc1ldbdviOiIM58PGdKC7XEiscmqlvLraBwbt3koTkF2v a+LyQaEhSYl/Y0XZ+uQPwGpS35M4s0XZDccQtLz0fR1dz0pOVbxJMcJOW0b4u0BfvoxG 4X3w== Received: from [192.168.0.41] (75-166-111-174.hlrn.qwest.net. [75.166.111.174]) by smtp.gmail.com with ESMTPSA id d77sm7559923pfd.126.2019.12.06.16.19.37 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Fri, 06 Dec 2019 16:19:38 -0800 (PST) Subject: [PING 3][PATCH] track dynamic allocation in strlen (PR 91582) From: Martin Sebor To: gcc-patches , Jeff Law References: <081f0235-96b3-988b-5605-ed5a0805b3c7@gmail.com> <5cf529c3-f69a-46c8-93fc-cce673e02b5c@gmail.com> Message-ID: <4e6a8395-0bc7-8820-7222-99a3bf3d2f3a@gmail.com> Date: Fri, 6 Dec 2019 17:19:36 -0700 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: X-IsSubscribed: yes With part 2 (below) of this work committed, I've rebased the patch on the top of trunk and on top of the updated part 1 (also below). Attached is the result, retested on x86_64-linux. [1] include size and offset in -Wstringop-overflow https://gcc.gnu.org/ml/gcc-patches/2019-12/msg00392.html [2] extend -Wstringop-overflow to allocated objects (committed in r278983) https://gcc.gnu.org/ml/gcc-patches/2019-12/msg00263.html On 11/25/19 10:54 AM, Martin Sebor wrote: > Ping: https://gcc.gnu.org/ml/gcc-patches/2019-11/msg00812.html > > On 11/18/19 11:23 AM, Martin Sebor wrote: >> Ping: https://gcc.gnu.org/ml/gcc-patches/2019-11/msg00812.html >> >> On 11/11/19 6:27 PM, Martin Sebor wrote: >>> The attached patch extends the strlen pass to detect out-of-bounds >>> accesses to memory allocated by calls to other allocation functions >>> besides calloc and malloc, as well as VLAs, and user-defined >>> functions declared with attribute alloc_size.  There is some >>> overlap with the _FORTIFY_SOURCE detection but thanks to >>> the extensive use of ranges, this enhancement detects many more >>> cases of overflow. >>> >>> The solution primarily improves warnings but some of the changes >>> also improve codegen in some cases as a side-effect.  I hope to >>> take better advantage of the optimization opportunities the dynamic >>> memory tracking opens up (and also better buffer overflow and array >>> out-of-bounds detection) in GCC 11. >>> >>> Although the strlen pass already tracks some dynamic memory calls >>> (calloc and malloc) rather than extending the same infrastructure >>> (strinfo::stmt) to others I took the approach of adding a separate >>> data member for the other calls (strinfo::alloc) and tracking those >>> independently.  I did this to keep the changes only minimally >>> intrusive.  In the future (post GCC 10) it might be worth >>> considering merging both. >>> >>> Besides introducing the new member and making use of it, the rest >>> of the changes were prompted by weaknesses exposed by test cases >>> involving dynamically allocated objects. >>> >>> The patch is intended to apply on top of the two related patches >>> posted last week ([1] and [2]).  For all tests to pass also expects >>> the fix for PR 92412 posted earlier today ([3]). >>> >>> Martin >>> >>> [1] https://gcc.gnu.org/ml/gcc-patches/2019-11/msg00429.html >>> [2] https://gcc.gnu.org/ml/gcc-patches/2019-11/msg00652.html >>> [3] https://gcc.gnu.org/ml/gcc-patches/2019-11/msg00800.html >> > PR middle-end/91582 - missing heap overflow detection for strcpy gcc/ChangeLog: PR middle-end/91582 * builtins.c (gimple_call_alloc_size): Add argument. * builtins.h (gimple_call_alloc_size): Same. * tree-ssa-strlen.c (strinfo::alloc): New member. (get_addr_stridx): Add argument. (get_stridx): Use ptrdiff_t. Add argument. (new_strinfo): Set new member. (get_string_length): Handle alloca and VLA. (dump_strlen_info): Dump more state. (maybe_invalidate): Print more info. Decrease indentation. (unshare_strinfo): Set new member. (valid_builtin_call): Handle alloca and VLA. (maybe_warn_overflow): Check and set no-warning bit. Improve handling of offsets. Print allocated objects. (handle_builtin_strlen): Handle strinfo records with null lengths. (handle_builtin_strcpy): Add argument. Call maybe_warn_overflow. (is_strlen_related_p): Handle dynamically allocated objects. (get_range): Add argument. (handle_builtin_malloc): Rename... (handle_aalloc): ...to this and handle all allocation functions. (handle_builtin_memset): Call maybe_warn_overflow. (count_nonzero_bytes): Handle more MEM_REF forms. (strlen_check_and_optimize_call): Call handle_alloc_call. Pass arguments to more callees. (handle_integral_assign): Add argument. Create strinfo entries for MEM_REF assignments. (check_and_optimize_stmt): Handle more MEM_REF forms. gcc/testsuite/ChangeLog: PR middle-end/91582 * c-c++-common/Wrestrict.c: Adjust expected warnings. * gcc.dg/Warray-bounds-46.c: Disable -Wstringop-overflow. * gcc.dg/Warray-bounds-47.c: Same. * gcc.dg/Warray-bounds-52.c: New test. * gcc.dg/Wstringop-overflow-26.c: New test. * gcc.dg/Wstringop-overflow-27.c: New test. * gcc.dg/Wstringop-overflow-28.c: New test. * gcc.dg/attr-alloc_size.c (test): Disable -Warray-bounds. * gcc.dg/attr-copy-2.c: Adjust expected warnings. * gcc.dg/builtin-stringop-chk-5.c: Adjust text of expected messages. * gcc.dg/strlenopt-86.c: Relax test. * gcc.target/i386/pr82002-1.c: Prune expected warnings. diff --git a/gcc/builtins.c b/gcc/builtins.c index 1ee84f343a3..5db0bd3226c 100644 --- a/gcc/builtins.c +++ b/gcc/builtins.c @@ -48,6 +48,7 @@ along with GCC; see the file COPYING3. If not see #include "calls.h" #include "varasm.h" #include "tree-object-size.h" +#include "tree-ssa-strlen.h" #include "realmpfr.h" #include "cfgrtl.h" #include "except.h" @@ -3697,10 +3698,12 @@ check_access (tree exp, tree, tree, tree dstwrite, } /* If STMT is a call to an allocation function, returns the size - of the object allocated by the call. */ + of the object allocated by the call. If nonnull, set RNG1[] + to the range of the size. */ tree -gimple_call_alloc_size (gimple *stmt) +gimple_call_alloc_size (gimple *stmt, wide_int rng1[2] /* = NULL */, + const vr_values *rvals /* = NULL */) { if (!stmt) return NULL_TREE; @@ -3747,11 +3750,12 @@ gimple_call_alloc_size (gimple *stmt) tree size = gimple_call_arg (stmt, argidx1); - wide_int rng1[2]; - if (TREE_CODE (size) == INTEGER_CST) - rng1[0] = rng1[1] = wi::to_wide (size); - else if (TREE_CODE (size) != SSA_NAME - || get_range_info (size, rng1, rng1 + 1) != VR_RANGE) + wide_int rng1_buf[2]; + /* If RNG1 is not set, use the buffer. */ + if (!rng1) + rng1 = rng1_buf; + + if (!get_range (size, rng1, rvals)) return NULL_TREE; if (argidx2 > nargs && TREE_CODE (size) == INTEGER_CST) @@ -3761,10 +3765,7 @@ gimple_call_alloc_size (gimple *stmt) of the upper bounds as a constant. Ignore anti-ranges. */ tree n = argidx2 < nargs ? gimple_call_arg (stmt, argidx2) : integer_one_node; wide_int rng2[2]; - if (TREE_CODE (n) == INTEGER_CST) - rng2[0] = rng2[1] = wi::to_wide (n); - else if (TREE_CODE (n) != SSA_NAME - || get_range_info (n, rng2, rng2 + 1) != VR_RANGE) + if (!get_range (n, rng2, rvals)) return NULL_TREE; /* Extend to the maximum precsion to avoid overflow. */ @@ -3802,7 +3803,7 @@ gimple_call_alloc_size (gimple *stmt) tree compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */, - tree *poff /* = NULL */) + tree *poff /* = NULL */, const vr_values *rvals /* = NULL */) { tree dummy_decl = NULL_TREE; if (!pdecl) @@ -3826,8 +3827,14 @@ compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */, if (is_gimple_call (stmt)) { /* If STMT is a call to an allocation function get the size - from its argument(s). */ - return gimple_call_alloc_size (stmt); + from its argument(s). If successful, also set *PDECL to + DEST for the caller to include in diagnostics. */ + if (tree size = gimple_call_alloc_size (stmt)) + { + *pdecl = dest; + return size; + } + return NULL_TREE; } if (!is_gimple_assign (stmt)) @@ -3857,13 +3864,13 @@ compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */, ; else if (wi::ltu_p (wioff, wisiz)) { - *poff = size_binop (PLUS_EXPR, *poff, off); + *poff = *poff ? size_binop (PLUS_EXPR, *poff, off) : off; return wide_int_to_tree (TREE_TYPE (size), wi::sub (wisiz, wioff)); } else { - *poff = size_binop (PLUS_EXPR, *poff, off); + *poff = *poff ? size_binop (PLUS_EXPR, *poff, off) : off; return size_zero_node; } } @@ -3888,15 +3895,15 @@ compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */, ; else if (wi::ltu_p (min, wisiz)) { - *poff = size_binop (PLUS_EXPR, *poff, - wide_int_to_tree (sizetype, min)); + tree t = wide_int_to_tree (sizetype, min); + *poff = *poff ? size_binop (PLUS_EXPR, *poff, t) : t; return wide_int_to_tree (TREE_TYPE (size), wi::sub (wisiz, min)); } else { - *poff = size_binop (PLUS_EXPR, *poff, - wide_int_to_tree (sizetype, min)); + tree t = wide_int_to_tree (sizetype, min); + *poff = *poff ? size_binop (PLUS_EXPR, *poff, t) : t; return size_zero_node; } } @@ -3926,10 +3933,19 @@ compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */, && *poff && integer_zerop (*poff)) return size_zero_node; - /* A valid offset into a declared object cannot be negative. */ - if (tree_int_cst_sgn (*poff) < 0) + /* A valid offset into a declared object cannot be negative. + A zero size with a zero "inner" offset is still zero size + regardless of the "other" offset OFF. */ + if (*poff + && ((integer_zerop (*poff) && integer_zerop (size)) + || (TREE_CODE (*poff) == INTEGER_CST + && tree_int_cst_sgn (*poff) < 0))) return size_zero_node; + wide_int offrng[2]; + if (!get_range (off, offrng, rvals)) + return NULL_TREE; + /* Adjust SIZE either up or down by the sum of *POFF and OFF above. */ if (TREE_CODE (dest) == ARRAY_REF) @@ -3938,24 +3954,28 @@ compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */, tree eltype = TREE_TYPE (dest); tree tpsize = TYPE_SIZE_UNIT (eltype); if (tpsize && TREE_CODE (tpsize) == INTEGER_CST) - off = fold_build2 (MULT_EXPR, size_type_node, off, tpsize); + { + wide_int wsz = wi::to_wide (tpsize, offrng->get_precision ()); + offrng[0] *= wsz; + offrng[1] *= wsz; + } else return NULL_TREE; } - wide_int offrng[2]; - if (TREE_CODE (off) == INTEGER_CST) - offrng[0] = offrng[1] = wi::to_wide (off); - else if (TREE_CODE (off) == SSA_NAME) + if (!*poff) { - wide_int min, max; - enum value_range_kind rng - = get_range_info (off, offrng, offrng + 1); - if (rng != VR_RANGE) - return NULL_TREE; + /* If the "inner" offset is unknown and the "outer" offset + is either negative or less than SIZE, return the size + minus the offset. This may be overly optimistic in + the first case if the inner offset happens to be less + than the absolute value of the outer offset. */ + off = fold_convert (ptrdiff_type_node, off); + if (tree_int_cst_sgn (off) < 0 + || tree_int_cst_lt (off, size)) + return fold_build2 (MINUS_EXPR, size_type_node, size, off); + return integer_zero_node; } - else - return NULL_TREE; /* Convert to the same precision to keep wide_int from "helpfuly" crashing whenever it sees other argumments. */ @@ -4022,6 +4042,9 @@ compute_objsize (tree dest, int ostype, tree *pdecl /* = NULL */, return component_ref_size (dest); } + if (TREE_CODE (dest) == VAR_DECL) + return DECL_SIZE_UNIT (dest); + if (TREE_CODE (dest) != ADDR_EXPR) return NULL_TREE; diff --git a/gcc/builtins.h b/gcc/builtins.h index 0fcccc12a39..2736f161b6b 100644 --- a/gcc/builtins.h +++ b/gcc/builtins.h @@ -133,8 +133,12 @@ extern tree fold_call_stmt (gcall *, bool); extern void set_builtin_user_assembler_name (tree decl, const char *asmspec); extern bool is_simple_builtin (tree); extern bool is_inexpensive_builtin (tree); -extern tree gimple_call_alloc_size (gimple *); -extern tree compute_objsize (tree, int, tree * = NULL, tree * = NULL); + +class vr_values; +tree gimple_call_alloc_size (gimple *, wide_int[2] = NULL, + const vr_values * = NULL); +extern tree compute_objsize (tree, int, tree * = NULL, tree * = NULL, + const vr_values * = NULL); extern bool readonly_data_expr (tree exp); extern bool init_target_chars (void); diff --git a/gcc/testsuite/c-c++-common/Wrestrict.c b/gcc/testsuite/c-c++-common/Wrestrict.c index c852b06bbd7..1903f502abd 100644 --- a/gcc/testsuite/c-c++-common/Wrestrict.c +++ b/gcc/testsuite/c-c++-common/Wrestrict.c @@ -731,10 +731,16 @@ void test_strcpy_range (void) r = SR (3, DIFF_MAX - 3); T (8, "01", a + r, a); - T (8, "012", a + r, a); /* { dg-warning "accessing 4 bytes at offsets \\\[3, \[0-9\]+] and 0 may overlap 1 byte at offset 3" "strcpy" } */ + + /* The accesses below might trigger either + -Wrestrict: accessing 4 bytes at offsets [3, \[0-9\]+] and 0 may overlap 1 byte at offset 3 + or + -Wstringop-overflow: writing 4 bytes into a region of size 0 + Either of the two is appropriate. */ + T (8, "012", a + r, a); /* { dg-warning "\\\[-Wrestrict|-Wstringop-overflow" } */ r = SR (DIFF_MAX - 2, DIFF_MAX - 1); - T (8, "012", a + r, a); /* { dg-warning "accessing 4 bytes at offsets \\\[\[0-9\]+, \[0-9\]+] and 0 overlaps" "strcpy" } */ + T (8, "012", a + r, a); /* { dg-warning "\\\[-Wrestrict|-Wstringop-overflow" } */ /* Exercise the full range of ptrdiff_t. */ r = signed_value (); diff --git a/gcc/testsuite/gcc.dg/Warray-bounds-46.c b/gcc/testsuite/gcc.dg/Warray-bounds-46.c index 4980f93a470..74e78cbdbe8 100644 --- a/gcc/testsuite/gcc.dg/Warray-bounds-46.c +++ b/gcc/testsuite/gcc.dg/Warray-bounds-46.c @@ -3,7 +3,7 @@ Test to verify that past-the-end accesses by string functions to member arrays by-reference objects are diagnosed. { dg-do compile } - { dg-options "-O2 -Wall -Wno-unused-local-typedefs -ftrack-macro-expansion=0" } */ + { dg-options "-O2 -Wall -Wno-unused-local-typedefs -Wno-stringop-overflow -ftrack-macro-expansion=0" } */ #define SA(expr) typedef int StaticAssert [2 * !!(expr) - 1] diff --git a/gcc/testsuite/gcc.dg/Warray-bounds-47.c b/gcc/testsuite/gcc.dg/Warray-bounds-47.c index 06ad488d1e0..848ef365163 100644 --- a/gcc/testsuite/gcc.dg/Warray-bounds-47.c +++ b/gcc/testsuite/gcc.dg/Warray-bounds-47.c @@ -1,7 +1,7 @@ /* PR middle-end/91830 - Bogus -Warray-bounds on strcpy into a member of a subobject compiling binutils { dg-do compile } - { dg-options "-O2 -Wall -ftrack-macro-expansion=0" } */ + { dg-options "-O2 -Wall -Wno-stringop-overflow -ftrack-macro-expansion=0" } */ extern char* strcpy (char*, const char*); extern void sink (void*); diff --git a/gcc/testsuite/gcc.dg/attr-alloc_size.c b/gcc/testsuite/gcc.dg/attr-alloc_size.c index 7b0dc6e4535..4c0cd9a14c4 100644 --- a/gcc/testsuite/gcc.dg/attr-alloc_size.c +++ b/gcc/testsuite/gcc.dg/attr-alloc_size.c @@ -22,15 +22,15 @@ test (void) strcpy (p, "Hello"); p = malloc1 (6); strcpy (p, "Hello"); - strcpy (p, "Hello World"); /* { dg-warning "writing" "strcpy" } */ + strcpy (p, "Hello World"); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" "strcpy" } */ p = malloc2 (__INT_MAX__ >= 1700000 ? 424242 : __INT_MAX__ / 4, 6); strcpy (p, "World"); - strcpy (p, "Hello World"); /* { dg-warning "writing" "strcpy" } */ + strcpy (p, "Hello World"); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" "strcpy" } */ p = calloc1 (2, 5); strcpy (p, "World"); - strcpy (p, "Hello World"); /* { dg-warning "writing" "strcpy" } */ + strcpy (p, "Hello World"); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" "strcpy" } */ p = calloc2 (2, __INT_MAX__ >= 1700000 ? 424242 : __INT_MAX__ / 4, 5); strcpy (p, "World"); - strcpy (p, "Hello World"); /* { dg-warning "writing" "strcpy" } */ + strcpy (p, "Hello World"); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" "strcpy" } */ } diff --git a/gcc/testsuite/gcc.dg/attr-copy-2.c b/gcc/testsuite/gcc.dg/attr-copy-2.c index f311ca32aa6..ffc7208f4a7 100644 --- a/gcc/testsuite/gcc.dg/attr-copy-2.c +++ b/gcc/testsuite/gcc.dg/attr-copy-2.c @@ -99,7 +99,7 @@ void* xref12 (int); void* call_xref12 (void) { void *p = xref12 (3); - __builtin___strcpy_chk (p, "123", __builtin_object_size (p, 0)); /* { dg-warning "\\\[-Wstringop-overflow=]" } */ + __builtin___strcpy_chk (p, "123", __builtin_object_size (p, 0)); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" } */ return p; } @@ -197,7 +197,7 @@ void* falias_malloc (void); void* call_falias_malloc (void) { char *p = falias_malloc (); - __builtin___strcpy_chk (p, "123", __builtin_object_size (p, 0)); /* { dg-warning "\\\[-Wstringop-overflow=]" } */ + __builtin___strcpy_chk (p, "123", __builtin_object_size (p, 0)); /* { dg-warning "\\\[-Warray-bounds|-Wstringop-overflow" } */ return p; } diff --git a/gcc/testsuite/gcc.dg/builtin-stringop-chk-5.c b/gcc/testsuite/gcc.dg/builtin-stringop-chk-5.c index 320cd51fcf2..87dd6ac4e89 100644 --- a/gcc/testsuite/gcc.dg/builtin-stringop-chk-5.c +++ b/gcc/testsuite/gcc.dg/builtin-stringop-chk-5.c @@ -110,7 +110,7 @@ void test_memop_warn_alloc (const void *src) struct A *a = __builtin_malloc (sizeof *a * 2); - memcpy (a, src, n); /* { dg-warning "writing between 8 and 32 bytes into a region of size 4 overflows the destination" "memcpy into allocated" } */ + memcpy (a, src, n); /* { dg-warning "writing between 8 and 32 bytes into a region of size 4 " "memcpy into allocated" } */ escape (a, src); /* At -Wstringop-overflow=1 the destination is considered to be @@ -127,7 +127,7 @@ void test_memop_warn_alloc (const void *src) struct B *b = __builtin_malloc (sizeof *b * 2); - memcpy (&b[0], src, n); /* { dg-warning "writing between 12 and 32 bytes into a region of size 8 overflows the destination" "memcpy into allocated" } */ + memcpy (&b[0], src, n); /* { dg-warning "writing between 12 and 32 bytes into a region of size 8 " "memcpy into allocated" } */ escape (b); /* The following idiom of clearing multiple members of a struct is diff --git a/gcc/testsuite/gcc.dg/strlenopt-86.c b/gcc/testsuite/gcc.dg/strlenopt-86.c index 3e86fa3c90a..d2029443556 100644 --- a/gcc/testsuite/gcc.dg/strlenopt-86.c +++ b/gcc/testsuite/gcc.dg/strlenopt-86.c @@ -9,11 +9,11 @@ unsigned n0, n1; void* -keep_strlen_calloc_store_cst_memset (unsigned a, unsigned b) +keep_strlen_calloc_store_cst_memset (int i, unsigned a, unsigned b) { char *p = __builtin_calloc (a, 1); - p[1] = 'x'; + p[i] = 'x'; __builtin_memset (p, 0, b); @@ -23,11 +23,11 @@ keep_strlen_calloc_store_cst_memset (unsigned a, unsigned b) } void* -keep_strlen_calloc_store_var_memset (int x, unsigned a, unsigned b) +keep_strlen_calloc_store_var_memset (int i, int x, unsigned a, unsigned b) { char *p = __builtin_calloc (a, 1); - p[1] = x; + p[i] = x; __builtin_memset (p, 0, b); @@ -37,11 +37,11 @@ keep_strlen_calloc_store_var_memset (int x, unsigned a, unsigned b) } void* -keep_strlen_calloc_store_memset_2 (int x, unsigned a, unsigned b, unsigned c) +keep_strlen_calloc_store_memset_2 (int i, int x, unsigned a, unsigned b, unsigned c) { char *p = __builtin_calloc (a, 1); - p[1] = x; + p[i] = x; __builtin_memset (p, 0, b); n0 = __builtin_strlen (p); diff --git a/gcc/testsuite/gcc.target/i386/pr82002-1.c b/gcc/testsuite/gcc.target/i386/pr82002-1.c index 86678a01992..b4d4bd3d125 100644 --- a/gcc/testsuite/gcc.target/i386/pr82002-1.c +++ b/gcc/testsuite/gcc.target/i386/pr82002-1.c @@ -10,3 +10,5 @@ b () a (c); a (c); } + +// { dg-prune-output "\\\[-Wstringop-overflow" } diff --git a/gcc/tree-ssa-strlen.h b/gcc/tree-ssa-strlen.h index 4d43fc65e9e..46f2c0a3996 100644 --- a/gcc/tree-ssa-strlen.h +++ b/gcc/tree-ssa-strlen.h @@ -25,8 +25,10 @@ extern bool is_strlen_related_p (tree, tree); extern bool maybe_diag_stxncpy_trunc (gimple_stmt_iterator, tree, tree); extern tree set_strlen_range (tree, wide_int, wide_int, tree = NULL_TREE); -struct c_strlen_data; class vr_values; +extern tree get_range (tree, wide_int[2], const vr_values * = NULL); + +struct c_strlen_data; extern void get_range_strlen_dynamic (tree , c_strlen_data *, const vr_values *); /* APIs internal to strlen pass. Defined in in gimple-ssa-sprintf.c. */ diff --git a/gcc/testsuite/gcc.dg/Warray-bounds-52.c b/gcc/testsuite/gcc.dg/Warray-bounds-52.c new file mode 100644 index 00000000000..1a7d76fcc2a --- /dev/null +++ b/gcc/testsuite/gcc.dg/Warray-bounds-52.c @@ -0,0 +1,97 @@ +/* PR middle-end/92341 - missing -Warray-bounds indexing past the end + of a compound literal + { dg-do compile } + { dg-options "-O2 -Wall -ftrack-macro-expansion=0" } */ + +#include "range.h" + +#define INT_MAX __INT_MAX__ +#define INT_MIN (-__INT_MAX__ - 1) + +void sink (int, ...); + + +#define T(...) sink (__LINE__, (__VA_ARGS__)) + + +void direct_idx_cst (void) +{ + T ((int[]){ }[-1]); // { dg-warning "array subscript -1 is outside array bounds of 'int\\\[0]'" } + T ((int[]){ }[0]); // { dg-warning "array subscript 0 is outside array bounds of 'int\\\[0]'" } + T ((int[]){ }[1]); // { dg-warning "array subscript 1 is outside array bounds of 'int\\\[0]'" } + + T ((int[]){ 1 }[-1]); // { dg-warning "array subscript -1 is below array bounds of 'int\\\[1]'" } + T ((int[]){ 1 }[0]); + T ((int[]){ 1 }[1]); // { dg-warning "array subscript 1 is above array bounds of 'int\\\[1]'" } + T ((int[]){ 1 }[INT_MIN]); // { dg-warning "array subscript -\[0-9\]+ is below array bounds of 'int\\\[1]'" } + T ((int[]){ 1 }[INT_MAX]); // { dg-warning "array subscript \[0-9\]+ is above array bounds of 'int\\\[1]'" } + T ((int[]){ 1 }[SIZE_MAX]); // { dg-warning "array subscript \[0-9\]+ is above array bounds of 'int\\\[1]'" } +} + + +void direct_idx_var (int i) +{ + T ((char[]){ }[i]); // { dg-warning "array subscript i is outside array bounds of 'char\\\[0]'" } + T ((int[]){ }[i]); // { dg-warning "array subscript i is outside array bounds of 'int\\\[0]'" } +} + + +void direct_idx_range (void) +{ + ptrdiff_t i = SR (-2, -1); + + T ((int[]){ 1 }[i]); // { dg-warning "array subscript \[ \n\r]+ is outside array bounds of 'int\\\[0]'" "pr?????" { xfail *-*-* } } +} + + +#undef T +#define T(idx, ...) do { \ + int *p = (__VA_ARGS__); \ + sink (p[idx]); \ + } while (0) + +void ptr_idx_cst (void) +{ + T (-1, (int[]){ }); // { dg-warning "array subscript -1 is outside array bounds of 'int\\\[0]'" } + T ( 0, (int[]){ }); // { dg-warning "array subscript 0 is outside array bounds of 'int\\\[0]'" } + T (+1, (int[]){ }); // { dg-warning "array subscript 1 is outside array bounds of 'int\\\[0]'" } + + T (-1, (int[]){ 1 }); // { dg-warning "array subscript -1 is outside array bounds of 'int\\\[1]'" } + T ( 0, (int[]){ 1 }); + T (+1, (int[]){ 1 }); // { dg-warning "array subscript 1 is outside array bounds of 'int\\\[1]'" } + T (INT_MIN, (int[]){ 1 }); // { dg-warning "array subscript -\[0-9\]+ is outside array bounds of 'int\\\[1]'" "lp64" { xfail ilp32 } } + T (INT_MAX, (int[]){ 1 }); // { dg-warning "array subscript \[0-9\]+ is outside array bounds of 'int\\\[1]'" "lp64" { target lp64 } } + // { dg-warning "array subscript -1 is outside array bounds of 'int\\\[1]'" "ilp32" { target ilp32 } .-1 } + T (SIZE_MAX, (int[]){ 1 }); // { dg-warning "array subscript -?\[0-9\]+ is outside array bounds of 'int\\\[1]'" } +} + + +void ptr_idx_var (int i) +{ + T (i, (int[]){ }); // { dg-warning "array subscript \[^\n\r\]+ is outside array bounds of 'int\\\[0]'" } + T (i, (int[]){ 1 }); + T (i, (int[]){ i, 1 }); +} + +void ptr_idx_range (void) +{ + ptrdiff_t i = SR (-2, -1); + + T (i, (int[]){ }); // { dg-warning "array subscript \\\[-2, -1] is outside array bounds of 'int\\\[0]'" } + T (i, (int[]){ 1 }); // { dg-warning "array subscript \\\[-2, -1] is outside array bounds of 'int\\\[1]'" } + T (i, (int[]){ i }); // { dg-warning "array subscript \\\[-2, -1] is outside array bounds of 'int\\\[1]'" } + + i = SR (0, 1); + + T (i, (int[]){ }); // { dg-warning "array subscript \\\[0, 1] is outside array bounds of 'int\\\[0]'" } + T (i, (int[]){ 1 }); + + i = SR (1, 2); + T (i, (int[]){ 1 }); // { dg-warning "array subscript \\\[1, 2] is outside array bounds of 'int\\\[1]'" } + + i = SR (2, 3); + T (i, (int[]){ 1, 2, 3 }); + + i = SR (3, 4); + T (i, (int[]){ 2, 3, 4 }); // { dg-warning "array subscript \\\[3, 4] is outside array bounds of 'int\\\[3]'" } +} diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-26.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-26.c new file mode 100644 index 00000000000..b59f90d2f8e --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-26.c @@ -0,0 +1,390 @@ +/* PR middle-end/91582 - missing heap overflow detection for strcpy + { dg-do compile } + { dg-options "-O2 -Wall -Wno-array-bounds -Wno-memset-transposed-args -ftrack-macro-expansion=0" } */ + +#include "range.h" + +#define INT_MAX __INT_MAX__ +#define INT_MIN (-INT_MAX - 1) + +typedef __SIZE_TYPE__ size_t; + +extern void* calloc (size_t, size_t); +extern void* malloc (size_t); +extern void* memset (void*, int, size_t); +extern char* strcpy (char*, const char*); +extern size_t strlen (const char*); + +void sink (void*); + +#define T(N, ACCESS) \ + do { \ + char *p = ALLOC (N); \ + ACCESS; \ + sink (p); \ + } while (0) + + +void test_byte_store_malloc (void) +{ +#define ALLOC __builtin_malloc + + T (1, p[-1] = 0); + // { dg-warning "writing 1 byte into a region of size 0 " "warning" { target *-*-* } .-1 } + // { dg-message "at offset -1 to an object with size 1 allocated by '__builtin_malloc'" "note" { target *-*-* } .-2 } + + T (1, p[ 0] = 0); + + T (1, p[ 1] = 0); + // { dg-warning "writing 1 byte into a region of size 0 " "warning" { target *-*-* } .-1 } + // { dg-message "at offset 1 to an object with size 1 allocated by '__builtin_malloc'" "note" { target *-*-* } .-2 } + + T (1, p[ 2] = 0); + // { dg-warning "writing 1 byte into a region of size 0 " "warning" { target *-*-* } .-1 } + // { dg-message "at offset 2 to an object with size 1 allocated by '__builtin_malloc'" "note" { target *-*-* } .-2 } + + T (9, p[-2] = 0); + // { dg-warning "writing 1 byte into a region of size 0 " "warning" { target *-*-* } .-1 } + // { dg-message "at offset -2 to an object with size 9 allocated by '__builtin_malloc'" "note" { target *-*-* } .-2 } + + T (9, p[ 0] = 0); + T (9, p[ 8] = 0); + T (9, p[ 9] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + + // Exercise boundary conditions. + T (1, p[DIFF_MIN] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + // { dg-message "at offset -\[1-9\]\[0-9\]+ to an object with size 1 allocated by '__builtin_malloc'" "note" { target *-*-* } .-1 } + T (1, p[INT_MIN] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + T (1, p[DIFF_MAX] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + T (1, p[SIZE_MAX] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + + size_t i = UR (3, 5); + + T (1, p[i] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + // { dg-message "at offset \\\[3, 5] to an object with size 1 allocated by '__builtin_malloc'" "note" { target *-*-* } .-1 } + T (3, p[i] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + T (4, p[i] = 0); + + size_t n = UR (6, 7); + + T (n, p[-1] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + // { dg-message "at offset -1 to an object with size between 6 and 7 allocated by '__builtin_malloc'" "note" { target *-*-* } .-1 } + T (n, p[ 0] = 0); + T (n, p[ 6] = 0); + T (n, p[ 7] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + + T (n, p[i] = 0); + T (n, p[i + 3] = 0); + T (n, p[i + 4] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + // { dg-message "at offset \\\[7, 9] to an object with size between 6 and 7 allocated by '__builtin_malloc' here" "note" { target *-*-* } .-1 } + T (n + 1, p[i + 4] = 0); + + char s[] = "123"; + n = strlen (s); + T (n, p[n] = 0); // { dg-warning "\\\[-Wstringop-overflow" "pr?????" { xfail *-*-* } } + // { dg-message "at offset 3 to an object with size 3 allocated by '__builtin_malloc' here" "note" { xfail *-*-* } .-1 } +} + + +void test_byte_store_alloca (void) +{ +#undef ALLOC +#define ALLOC __builtin_alloca + + T (1, p[-1] = 0); // { dg-warning "writing 1 byte into a region of size 0 " } + T (1, p[ 0] = 0); + T (1, p[ 1] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + T (1, p[ 2] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + + T (9, p[-1] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + T (9, p[ 0] = 0); + T (9, p[ 8] = 0); + T (9, p[ 9] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + + // Exercise boundary conditions. + T (1, p[DIFF_MIN] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + T (1, p[INT_MIN] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + T (1, p[DIFF_MAX] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + T (1, p[SIZE_MAX] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + + size_t i = UR (3, 5); + + T (1, p[i] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + T (3, p[i] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + T (4, p[i] = 0); + + size_t n = UR (6, 7); + + T (n, p[-1] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + T (n, p[ 0] = 0); + T (n, p[ 6] = 0); + T (n, p[ 7] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + + char s[] = "1234"; + n = strlen (s); + T (n, p[n] = 0); // { dg-warning "\\\[-Wstringop-overflow" "pr?????" { xfail *-*-* } } + // { dg-message "at offset 4 to an object with size 4 allocated by '__builtin_alloca' here" "note" { xfail *-*-* } .-1 } +} + + +void test_byte_store_vla (void) +{ + { + size_t n = 3; + char vla[n]; // { dg-message "at offset 3 to object 'vla\[^\\n\\r\'\]*' with size 3 declared here" "note" } + vla[3] = 0; // { dg-warning "writing 1 byte into a region of size 0 \\\[-Wstringop-overflow=]" } + sink (vla); + } + +#define VLA(N, ACCESS) \ + do { \ + size_t nelts = N; \ + char a[nelts]; \ + ACCESS; \ + sink (a); \ + } while (0) + + VLA (1, a[-2] = 0); // { dg-warning "writing 1 byte into a region of size 0 " } + VLA (1, a[-1] = 0); // { dg-warning "writing 1 byte into a region of size 0 " } + VLA (1, a[ 0] = 0); + VLA (1, a[ 1] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + VLA (1, a[ 2] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + + VLA (9, a[-10] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + // The following fails because the no-warning bit is set on the store + // for some unknown reason. + VLA (9, a[-1] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + VLA (9, a[ 0] = 0); + VLA (9, a[ 8] = 0); + VLA (9, a[ 9] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + + // Exercise boundary conditions. + VLA (1, a[DIFF_MIN] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + VLA (1, a[INT_MIN] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + VLA (1, a[DIFF_MAX] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + VLA (1, a[SIZE_MAX] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + + { + int i = SR (-5, -1); + + { + size_t n = 3; + char vla[n]; // { dg-message "at offset \\\[-5, -1] to object 'vla\[^\n\r\]*' with size 3 declared here" "note" } + vla[i] = 0; // { dg-warning "writing 1 byte into a region of size 0" } + sink (vla); + } + + VLA (1, a[i] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + VLA (3, a[i] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + VLA (4, a[i] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + } + + { + int i = SR (3, 5); + + { + size_t n = 3; + char vla[n]; // { dg-message "at offset \\\[3, 5] to object 'vla\[^\n\r\]*' with size 3 declared here" "note" } + vla[i] = 0; // { dg-warning "writing 1 byte into a region of size 0" } + sink (vla); + } + + VLA (1, a[i] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + VLA (3, a[i] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + VLA (4, a[i] = 0); + } + + { + size_t i = UR (3, 5); + + { + size_t n = 3; + char vla[n]; // { dg-message "at offset \\\[3, 5] to object 'vla\[^\n\r\]*' with size 3 declared here" "note" } + vla[i] = 0; // { dg-warning "writing 1 byte into a region of size 0" } + sink (vla); + } + + VLA (1, a[i] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + VLA (3, a[i] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + VLA (4, a[i] = 0); + } + + size_t n = UR (6, 7); + + VLA (n, a[-1] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + VLA (n, a[ 0] = 0); + VLA (n, a[ 6] = 0); + VLA (n, a[ 7] = 0); // { dg-warning "\\\[-Wstringop-overflow" } +} + +void test_byte_store_vla_strlen (void) +{ + char s[] = "12345"; + size_t n = strlen (s); + VLA (n, a[n] = 0); // { dg-warning "\\\[-Wstringop-overflow" "pr?????" { xfail *-*-* } } + // { dg-message "at offset 5 to an object with size 5 declared here" "note" { xfail *-*-* } .-1 } +} + + +void test_byte_store_alloc_size (void) +{ + extern __attribute__ ((alloc_size (1), malloc)) void* + alloc_1_size (size_t); + + { + size_t n = 3; + char *p = alloc_1_size (n); // { dg-message "at offset 3 to an object with size 3 allocated by 'alloc_1_size' here" "note" } + p[3] = 0; // { dg-warning "writing 1 byte into a region of size 0 \\\[-Wstringop-overflow=]" } + sink (p); + } + + extern __attribute__ ((alloc_size (2), malloc)) void* + alloc_2_int (size_t, int); + + { + int n = 5; + char *p = alloc_2_int (9, n); + p[4] = 0; + sink (p); + } + + extern __attribute__ ((alloc_size (2), malloc)) void* + alloc_2_int (size_t, int); + + { + int n = 5; + char *p = alloc_2_int (9, n); + p[4] = 0; + // The access below was not diagnosed because GCC didn't recognize + // that the pointer returned by malloc functions doesn't alias any + // live object in the process. I.e., it assumed that the store to + // p[4] above modified p itself. This is pr87313 but now it is + // diagnosed so something might be off with the analysis. + p[5] = 0; // { dg-warning "writing 1 byte into a region of size 0 \\\[-Wstringop-overflow=]" } + sink (p); + } + + { + int n = 5; + char *p = alloc_2_int (9, n); // { dg-message "at offset 5 to an object with size 5 allocated by 'alloc_2_int' here" "note" } + p[5] = 0; // { dg-warning "writing 1 byte into a region of size 0 \\\[-Wstringop-overflow=]" } + sink (p); + } + +#undef ALLOC +#define ALLOC alloc_1_size + + T (1, p[-1] = 0); // { dg-warning "writing 1 byte into a region of size 0 " } + T (1, p[ 0] = 0); + T (1, p[ 1] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + T (1, p[ 2] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + + T (9, p[-1] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + T (9, p[ 0] = 0); + T (9, p[ 8] = 0); + T (9, p[ 9] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + + // Exercise boundary conditions. + T (1, p[DIFF_MIN] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + T (1, p[INT_MIN] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + T (1, p[DIFF_MAX] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + T (1, p[SIZE_MAX] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + + size_t i = UR (3, 5); + + T (1, p[i] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + T (3, p[i] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + T (4, p[i] = 0); + + size_t n = UR (6, 7); + + T (n, p[-1] = 0); // { dg-warning "\\\[-Wstringop-overflow" } + T (n, p[ 0] = 0); + T (n, p[ 6] = 0); + T (n, p[ 7] = 0); // { dg-warning "\\\[-Wstringop-overflow" } +} + + +void test_memset_malloc (void) +{ +#undef ALLOC +#define ALLOC __builtin_malloc + + T (0, memset (p, 0, 0)); + T (0, memset (p, 0, 1)); // { dg-warning "writing 1 byte into a region of size 0 " } + + T (0, memset (p, 1, 0)); + T (0, memset (p, 1, 1)); // { dg-warning "writing 1 byte into a region of size 0 " } + + T (3, memset (p, 2, 0)); + T (3, memset (p, 2, 1)); + T (3, memset (p, 2, 3)); + T (3, memset (p, 2, 4)); // { dg-warning "writing 4 bytes into a region of size 3 " } + + T (3, memset (p + 1, 2, 1)); + T (3, memset (p + 1, 3, 2)); + T (3, memset (p + 1, 4, 3)); // { dg-warning "writing 3 bytes into a region of size 2 " } + + T (4, memset (&p[2], 2, 2)); + T (4, memset (&p[2], 4, 3)); // { dg-warning "writing 3 bytes into a region of size 2 " } + + T (3, memset (p, 5, INT_MAX)); // { dg-warning "\\\[-Wstringop-overflow" } + + size_t n = UR (3, 5); + + T (n, memset (p, 6, 0)); + T (n, memset (p, 7, 1)); + T (n, memset (p, 8, 3)); + T (n, memset (p, 9, 4)); + T (n, memset (p, 0, 5)); + T (n, memset (p, 1, 6)); // { dg-warning "writing 6 bytes into a region of size between 3 and 5" } + + int i = SR (-32767, 32767); + T (n, memset (p + i, 0, 1)); + T (n, memset (p + i, 0, 3)); + T (n, memset (p + i, 0, 5)); + T (n, memset (p + i, 0, 6)); // { dg-warning "writing 6 bytes into a region of size between 3 and 5" } +} + + +#define S(N) ("0123456789" + 10 - N) + +const char str0[] = ""; +const char str1[] = "0"; +const char str2[] = "01"; + +void test_strcpy_malloc (const void *s, size_t n) +{ + const char s0[] = ""; + const char s1[] = "0"; + const char s2[] = "01"; + + // Each of the strcpy calls below is represented differently. Verify + // that they are all handled. + // __builtin_memcpy (p_6, "", 1); + T (0, strcpy (p, "")); // { dg-warning "writing 1 byte into a region of size 0 " } + // MEM[(char * {ref-all})p_10] = 0; + T (0, strcpy (p, S (0))); // { dg-warning "writing 1 byte into a region of size 0 " } + // MEM[(char * {ref-all})p_14] = MEM[(char * {ref-all})&str0]; + T (0, strcpy (p, str0)); // { dg-warning "writing 1 byte into a region of size 0 " } + // s0 = ""; + // __builtin_memcpy (p_18, &s0, 1); + T (0, strcpy (p, s0)); // { dg-warning "writing 1 byte into a region of size 0 " } + + T (1, strcpy (p, "")); + T (1, strcpy (p, S (0))); + T (1, strcpy (p, str0)); + T (1, strcpy (p, s0)); + + T (1, strcpy (p, "1")); // { dg-warning "writing 2 bytes into a region of size 1 " } + // __builtin_memcpy (p_42, &MEM [(void *)"0123456789" + 9B], 2); + T (1, strcpy (p, S (1))); // { dg-warning "writing 2 bytes into a region of size 1 " } + // MEM [(char * {ref-all})p_46] = MEM [(char * {ref-all})&str1]; + T (1, strcpy (p, str1)); // { dg-warning "writing 2 bytes into a region of size 1 " } + T (1, strcpy (p, s1)); // { dg-warning "writing 2 bytes into a region of size 1 " } + + T (2, strcpy (p, S (0))); + T (2, strcpy (p, S (2))); // { dg-warning "writing 3 bytes into a region of size 2 " } + T (2, strcpy (p, str2)); // { dg-warning "writing 3 bytes into a region of size 2 " } + T (2, strcpy (p, s2)); // { dg-warning "writing 3 bytes into a region of size 2 " } +} diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-27.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-27.c new file mode 100644 index 00000000000..249ce2b6ad5 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-27.c @@ -0,0 +1,293 @@ +/* PR middle-end/91582 - missing heap overflow detection for strcpy + PR middle-end/85484 - missing -Wstringop-overflow for strcpy with + a string of non-const length + { dg-do compile } + { dg-options "-O2 -Wall -Wno-array-bounds" } */ + +typedef __SIZE_TYPE__ size_t; + +extern void* calloc (size_t, size_t); +extern void* malloc (size_t); +extern void* memcpy (void*, const void*, size_t); +extern void* memset (void*, int, size_t); +extern char* strcpy (char*, const char*); +extern size_t strlen (const char*); + +void sink (void*); + + +void test_memcpy_nowarn (const void *s, int i, size_t n) +{ + sink (memcpy (calloc (1, 1), s, 1)); + sink (memcpy (calloc (1, 2), s, 1)); + sink (memcpy (calloc (2, 1), s, 1)); + sink (memcpy (calloc (3, 1), s, 2)); + sink (memcpy (calloc (3, 1), "12", 2)); + sink (memcpy (calloc (3, 1), s, 3)); + sink (memcpy (calloc (3, 1), "12", 3)); + sink (memcpy (calloc (i, 1), s, 1)); + sink (memcpy (calloc (n, 1), s, 1)); + sink (memcpy (calloc (1, n), "", 1)); + sink (memcpy (calloc (1, i), "", 1)); + sink (memcpy (calloc (i, 1), "123", 3)); + sink (memcpy (calloc (n, 1), "123", 3)); + sink (memcpy (calloc (1, i), "123456", 7)); + sink (memcpy (calloc (1, n), "123456", 7)); + sink (memcpy (calloc (n, 1), s, 12345)); + sink (memcpy (calloc (1, n), s, n - 1)); + sink (memcpy (calloc (n, 1), s, n)); + + sink (memcpy ((char*)calloc (1, 1) + i, "123", 1)); + sink (memcpy ((char*)calloc (n, 1) + i, "123", n)); + + sink (memcpy ((char*)calloc (1, 1) + i, s, 1)); + sink (memcpy ((char*)calloc (n, 1) + i, s, n)); + + sink (memcpy (malloc (1), s, 1)); + sink (memcpy (malloc (2), s, 1)); + sink (memcpy (malloc (3), s, 2)); + sink (memcpy (malloc (3), "12", 2)); + sink (memcpy (malloc (3), s, 3)); + sink (memcpy (malloc (3), "12", 3)); + sink (memcpy (malloc (n), s, 1)); + sink (memcpy (malloc (n), "", 1)); + sink (memcpy (malloc (n), "123", 3)); + sink (memcpy (malloc (n), "123456", 7)); + sink (memcpy (malloc (n), s, 12345)); + sink (memcpy (malloc (n), s, n - 1)); + sink (memcpy (malloc (n), s, n)); + + { + const int a[] = { 1, 2, 3, 4 }; + void *p = (char*)malloc (sizeof a); + memcpy (p, a, sizeof a); + sink (p); + } + + { + const int a[] = { 1, 2, 3, 4, 5 }; + size_t nelts = sizeof a / sizeof *a; + int vla[nelts]; + memcpy (vla, a, nelts * sizeof *vla); + sink (vla); + } +} + + +void test_memcpy_warn (const int *s, size_t n) +{ + { + void *p = (char*)malloc (0); + memcpy (p, s, 1); // { dg-warning "writing 1 byte into a region of size 0" } + sink (p); + } + + { + void *p = (char*)malloc (1); + memcpy (p, s, 2); // { dg-warning "writing 2 bytes into a region of size 1" } + sink (p); + } + + { + void *p = (char*)malloc (2); + memcpy (p, s, 3); // { dg-warning "writing 3 bytes into a region of size 2" } + sink (p); + } + + { + void *p = (char*)malloc (3); + memcpy (p, s, 4); // { dg-warning "writing 4 bytes into a region of size 3" } + sink (p); + } + + { + const int a[] = { 1, 2, 3, 4 }; + void *p = (char*)malloc (sizeof *a); + memcpy (p, a, sizeof a); // { dg-warning "" } + sink (p); + } + + { + const int a[] = { 1, 2, 3, 4, 5 }; + size_t nelts = sizeof a / sizeof *a; + char vla[nelts]; + memcpy (vla, a, nelts * sizeof *a); // { dg-warning "" } + sink (vla); + } + + { + void *p = malloc (n); + memcpy (p, s, n * sizeof *s); // { dg-warning "\\\[-Wstringop-overflow" "" { xfail *-*-* } } + sink (p); + } +} + +void test_memset_nowarn (int x, size_t n) +{ + sink (memset (calloc (1, 1), x, 1)); + sink (memset (calloc (1, 2), x, 1)); + sink (memset (calloc (2, 1), x, 1)); + sink (memset (calloc (3, 1), x, 2)); + sink (memset (calloc (3, 1), x, 3)); + sink (memset (calloc (n, 1), x, 1)); + sink (memset (calloc (n, 1), x, 12345)); + sink (memset (calloc (1, n), x, n - 1)); + sink (memset (calloc (n, 1), x, n)); + + sink (memset (malloc (1), x, 1)); + sink (memset (malloc (2), x, 1)); + sink (memset (malloc (3), x, 2)); + sink (memset (malloc (3), x, 3)); + sink (memset (malloc (n), x, 1)); + sink (memset (malloc (n), x, 12345)); + sink (memset (malloc (n), x, n - 1)); + sink (memset (malloc (n), x, n)); + + { + const int a[] = { 1, 2, 3, 4 }; + void *p = (char*)malloc (sizeof a); + memset (p, x, sizeof a); + sink (p); + } + + { + const int a[] = { 1, 2, 3, 4, 5 }; + size_t nelts = sizeof a / sizeof *a; + int vla[nelts]; + memset (vla, x, nelts * sizeof *vla); + sink (vla); + } +} + + +void test_memset_warn (int x, size_t n) +{ + { + void *p = (char*)malloc (0); + memset (p, x, 1); // { dg-warning "writing 1 byte into a region of size 0" } + sink (p); + } + + { + void *p = (char*)malloc (1); + memset (p, x, 2); // { dg-warning "writing 2 bytes into a region of size 1" } + sink (p); + } + + { + void *p = (char*)malloc (2); + memset (p, x, 3); // { dg-warning "writing 3 bytes into a region of size 2" } + sink (p); + } + + { + void *p = (char*)malloc (3); + memset (p, x, 4); // { dg-warning "writing 4 bytes into a region of size 3" } + sink (p); + } + + { + const int a[] = { 1, 2, 3, 4 }; + void *p = (char*)malloc (sizeof *a); + memset (p, 0, sizeof a); // { dg-warning "" } + sink (p); + } + + { + const int a[] = { 1, 2, 3, 4, 5 }; + size_t nelts = sizeof a / sizeof *a; + char vla[nelts]; + memset (vla, 0, nelts * sizeof *a); // { dg-warning "" } + sink (vla); + } + + { + void *p = malloc (n); + memset (p, x, n * sizeof (int)); // { dg-warning "\\\[-Wstringop-overflow" "" { xfail *-*-* } } + sink (p); + } +} + + +void test_strcpy_nowarn (const char *s) +{ + { + const char a[] = "12"; + int n = strlen (a); + char *t = (char*)calloc (2, n); + strcpy (t, a); + sink (t); + } + + { + const char a[] = "123"; + unsigned n = strlen (a) + 1; + char *t = (char*)calloc (n, 1); + strcpy (t, a); + sink (t); + } + + { + const char a[] = "1234"; + size_t n = strlen (a) * 2; + char *t = (char*)malloc (n); + strcpy (t, a); + sink (t); + } + + { + const char a[] = "1234"; + size_t len = strlen (a) + 1; + char vla[len]; + strcpy (vla, a); + sink (vla); + } + + { + size_t n = strlen (s) + 1; + char *t = (char*)malloc (n); + strcpy (t, s); + sink (t); + } +} + + +void test_strcpy_warn (const char *s) +{ + { + const char a[] = "123"; + /* Verify that using signed int for the strlen result works (i.e., + that the conversion from signed int to size_t doesn't prevent + the detection. */ + int n = strlen (a); + char *t = (char*)calloc (n, 1); // { dg-message "at offset 0 to an object with size 3 allocated by 'calloc' here" "calloc note" { xfail *-*-* } } + // { dg-message "at offset 0 to an object with size at most 3 allocated by 'calloc' here" "calloc note" { target *-*-* } .-1 } + strcpy (t, a); // { dg-warning "writing 4 bytes into a region of size (between 0 and )?3 " } + + sink (t); + } + + { + const char a[] = "1234"; + size_t n = strlen (a); + char *t = (char*)malloc (n); // { dg-message "at offset 0 to an object with size 4 allocated by 'malloc' here" "malloc note" { xfail *-*-* } } + // { dg-message "at offset 0 to an object with size at most 4 allocated by 'malloc' here" "malloc note" { target *-*-* } .-1 } + strcpy (t, a); // { dg-warning "writing 5 bytes into a region of size (between 0 and )?4 " } + sink (t); + } + + // Exercise PR middle-end/85484. + { + size_t len = strlen (s); + char vla[len]; // { dg-message "at offset 0 to an object declared here" "vla note" } + strcpy (vla, s); // { dg-warning "writing one too many bytes into a region of a size that depends on 'strlen'" } + sink (vla); + } + + { + size_t n = strlen (s); + char *t = (char*)malloc (n); // { dg-message "at offset 0 to an object allocated by 'malloc' here" "malloc note" } + strcpy (t, s); // { dg-warning "writing one too many bytes into a region of a size that depends on 'strlen'" } + sink (t); + } +} diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-28.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-28.c new file mode 100644 index 00000000000..861c9206be4 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-28.c @@ -0,0 +1,32 @@ +/* Simple test to verify that constants computed by the strlen pass + are propagated through the CFG and subsequently made available + within the pass. + { dg-do compile } + { dg-options "-O2 -Wall " } */ + +typedef __SIZE_TYPE__ size_t; + +extern void* calloc (size_t, size_t); +extern void* malloc (size_t); +extern void* memcpy (void*, const void*, size_t); +extern void* memset (void*, int, size_t); +extern char* strcpy (char*, const char*); +extern size_t strlen (const char*); + +void sink (void*); + +void test_strlen_memcpy (void) +{ + char a[8] = "1234567"; + char b[7]; + + // The value of N is computed to be 7 below. Verify that it's + // available in the store to B[N] as well as in B[N + 1]. + size_t n = strlen (a); + memcpy (b, a, n); + b[n] = 0; // { dg-warning "writing 1 byte into a region of size 0" "constant propagation" { xfail *-*-* } } + sink (b); + + b[n + 1] = 0; // { dg-warning "writing 1 byte into a region of size 0" "constant propagation" { xfail *-*-* } } + sink (b); +} diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c index 212ac7152bf..525bb6550b4 100644 --- a/gcc/tree-ssa-strlen.c +++ b/gcc/tree-ssa-strlen.c @@ -61,6 +61,7 @@ along with GCC; see the file COPYING3. If not see #include "vr-values.h" #include "gimple-ssa-evrp-analyze.h" +#pragma GCC optimize ("0") /* A vector indexed by SSA_NAME_VERSION. 0 means unknown, positive value is an index into strinfo vector, negative value stands for string length of a string literal (~strlen). */ @@ -84,14 +85,20 @@ struct strinfo tree nonzero_chars; /* Any of the corresponding pointers for querying alias oracle. */ tree ptr; - /* This is used for two things: + /* STMT is used for two things: - To record the statement that should be used for delayed length computations. We maintain the invariant that all related strinfos have delayed lengths or none do. - - To record the malloc or calloc call that produced this result. */ + - To record the malloc or calloc call that produced this result + to optimize away malloc/memset sequences. STMT is reset after + a calloc-allocated object has been stored a non-zero value into. */ gimple *stmt; + /* Set to the dynamic allocation statement for the object (alloca, + calloc, malloc, or VLA). Unlike STMT, once set for a strinfo + object, ALLOC doesn't change. */ + gimple *alloc; /* Pointer to '\0' if known, if NULL, it can be computed as ptr + length. */ tree endptr; @@ -189,20 +196,20 @@ static int get_stridx_plus_constant (strinfo *, unsigned HOST_WIDE_INT, tree); static void handle_builtin_stxncpy (built_in_function, gimple_stmt_iterator *); /* Sets MINMAX to either the constant value or the range VAL is in - and returns true on success. When nonnull, uses RVALS to get - VAL's range. Otherwise uses get_range_info. */ + and returns either the constant value or VAL on success or null + when the range couldn't be determined . */ -static bool -get_range (tree val, wide_int minmax[2], const vr_values *rvals = NULL) +tree +get_range (tree val, wide_int minmax[2], const vr_values *rvals /* = NULL */) { - if (tree_fits_uhwi_p (val)) + if (TREE_CODE (val) == INTEGER_CST) { minmax[0] = minmax[1] = wi::to_wide (val); - return true; + return val; } if (TREE_CODE (val) != SSA_NAME) - return false; + return NULL_TREE; if (rvals) { @@ -215,20 +222,20 @@ get_range (tree val, wide_int minmax[2], const vr_values *rvals = NULL) = (CONST_CAST (class vr_values *, rvals)->get_value_range (val)); value_range_kind rng = vr->kind (); if (rng != VR_RANGE || !range_int_cst_p (vr)) - return false; + return NULL_TREE; minmax[0] = wi::to_wide (vr->min ()); minmax[1] = wi::to_wide (vr->max ()); - return true; + return val; } value_range_kind rng = get_range_info (val, minmax, minmax + 1); if (rng == VR_RANGE) - return true; + return val; /* Do not handle anti-ranges and instead make use of the on-demand VRP if/when it becomes available (hopefully in GCC 11). */ - return false; + return NULL_TREE; } /* Return: @@ -383,10 +390,10 @@ get_addr_stridx (tree exp, tree ptr, unsigned HOST_WIDE_INT *offset_out, must not be used in for functions that modify the string. */ static int -get_stridx (tree exp, wide_int offrng[2] = NULL) +get_stridx (tree exp, wide_int offrng[2] = NULL, const vr_values *rvals = NULL) { if (offrng) - offrng[0] = offrng[1] = wi::zero (TYPE_PRECISION (sizetype)); + offrng[0] = offrng[1] = wi::zero (TYPE_PRECISION (ptrdiff_type_node)); if (TREE_CODE (exp) == SSA_NAME) { @@ -465,7 +472,7 @@ get_stridx (tree exp, wide_int offrng[2] = NULL) return the index corresponding to the SSA_NAME. Do this irrespective of the whether the offset is known. */ - if (get_range (off, offrng)) + if (get_range (off, offrng, rvals)) { /* When the offset range is known, increment it it by the constant offset computed in prior @@ -672,6 +679,7 @@ new_strinfo (tree ptr, int idx, tree nonzero_chars, bool full_string_p) si->nonzero_chars = nonzero_chars; si->ptr = ptr; si->stmt = NULL; + si->alloc = NULL; si->endptr = NULL_TREE; si->refcount = 1; si->idx = idx; @@ -838,6 +846,8 @@ get_string_length (strinfo *si) if (chainsi->nonzero_chars == NULL) set_endptr_and_length (loc, chainsi, lhs); break; + case BUILT_IN_ALLOCA: + case BUILT_IN_ALLOCA_WITH_ALIGN: case BUILT_IN_MALLOC: break; /* BUILT_IN_CALLOC always has si->nonzero_chars set. */ @@ -885,45 +895,57 @@ dump_strlen_info (FILE *fp, gimple *stmt, const vr_values *rvals) fprintf (fp, ", ptr = "); print_generic_expr (fp, si->ptr); } - fprintf (fp, ", nonzero_chars = "); - print_generic_expr (fp, si->nonzero_chars); - if (TREE_CODE (si->nonzero_chars) == SSA_NAME) + + if (si->nonzero_chars) { - value_range_kind rng = VR_UNDEFINED; - wide_int min, max; - if (rvals) + fprintf (fp, ", nonzero_chars = "); + print_generic_expr (fp, si->nonzero_chars); + if (TREE_CODE (si->nonzero_chars) == SSA_NAME) { - const value_range_equiv *vr - = CONST_CAST (class vr_values *, rvals) - ->get_value_range (si->nonzero_chars); - rng = vr->kind (); - if (range_int_cst_p (vr)) + value_range_kind rng = VR_UNDEFINED; + wide_int min, max; + if (rvals) { - min = wi::to_wide (vr->min ()); - max = wi::to_wide (vr->max ()); + const value_range *vr + = CONST_CAST (class vr_values *, rvals) + ->get_value_range (si->nonzero_chars); + rng = vr->kind (); + if (range_int_cst_p (vr)) + { + min = wi::to_wide (vr->min ()); + max = wi::to_wide (vr->max ()); + } + else + rng = VR_UNDEFINED; } else - rng = VR_UNDEFINED; - } - else - rng = get_range_info (si->nonzero_chars, &min, &max); + rng = get_range_info (si->nonzero_chars, &min, &max); - if (rng == VR_RANGE || rng == VR_ANTI_RANGE) - { - fprintf (fp, " %s[%llu, %llu]", - rng == VR_RANGE ? "" : "~", - (long long) min.to_uhwi (), - (long long) max.to_uhwi ()); + if (rng == VR_RANGE || rng == VR_ANTI_RANGE) + { + fprintf (fp, " %s[%llu, %llu]", + rng == VR_RANGE ? "" : "~", + (long long) min.to_uhwi (), + (long long) max.to_uhwi ()); + } } } - fprintf (fp, " , refcount = %i", si->refcount); + + fprintf (fp, ", refcount = %i", si->refcount); if (si->stmt) { fprintf (fp, ", stmt = "); print_gimple_expr (fp, si->stmt, 0); } + if (si->alloc) + { + fprintf (fp, ", alloc = "); + print_gimple_expr (fp, si->alloc, 0); + } if (si->writable) fprintf (fp, ", writable"); + if (si->dont_invalidate) + fprintf (fp, ", dont_invalidate"); if (si->full_string_p) fprintf (fp, ", full_string_p"); if (strinfo *next = get_next_strinfo (si)) @@ -1197,80 +1219,87 @@ get_range_strlen_dynamic (tree src, c_strlen_data *pdata, BITMAP_FREE (visited); } -/* Invalidate string length information for strings whose length - might change due to stores in stmt, except those marked DON'T - INVALIDATE. For string-modifying statements, ZERO_WRITE is - set when the statement wrote only zeros. */ +/* Invalidate string length information for strings whose length might + change due to stores in STMT, except those marked DONT_INVALIDATE. + For string-modifying statements, ZERO_WRITE is set when the statement + wrote only zeros. + Returns true if any STRIDX_TO_STRINFO entries were considered + for invalidation. */ static bool maybe_invalidate (gimple *stmt, bool zero_write = false) { if (dump_file && (dump_flags & TDF_DETAILS)) - fprintf (dump_file, " %s()\n", __func__); + { + fprintf (dump_file, "%s called for ", __func__); + print_gimple_stmt (dump_file, stmt, TDF_LINENO); + } strinfo *si; - unsigned int i; bool nonempty = false; - for (i = 1; vec_safe_iterate (stridx_to_strinfo, i, &si); ++i) - if (si != NULL) - { - if (!si->dont_invalidate) - { - ao_ref r; - tree size = NULL_TREE; - if (si->nonzero_chars) - { - /* Include the terminating nul in the size of the string - to consider when determining possible clobber. */ - tree type = TREE_TYPE (si->nonzero_chars); - size = fold_build2 (PLUS_EXPR, type, si->nonzero_chars, - build_int_cst (type, 1)); - } - ao_ref_init_from_ptr_and_size (&r, si->ptr, size); - if (stmt_may_clobber_ref_p_1 (stmt, &r)) - { - if (dump_file && (dump_flags & TDF_DETAILS)) - { - if (size && tree_fits_uhwi_p (size)) - fprintf (dump_file, - " statement may clobber string " - HOST_WIDE_INT_PRINT_UNSIGNED " long\n", - tree_to_uhwi (size)); - else - fprintf (dump_file, - " statement may clobber string\n"); - } + for (unsigned i = 1; vec_safe_iterate (stridx_to_strinfo, i, &si); ++i) + { + if (si == NULL || !POINTER_TYPE_P (TREE_TYPE (si->ptr))) + continue; - set_strinfo (i, NULL); - free_strinfo (si); - continue; - } + nonempty = true; - if (size - && !zero_write - && si->stmt - && is_gimple_call (si->stmt) - && (DECL_FUNCTION_CODE (gimple_call_fndecl (si->stmt)) - == BUILT_IN_CALLOC)) - { - /* If the clobber test above considered the length of - the string (including the nul), then for (potentially) - non-zero writes that might modify storage allocated by - calloc consider the whole object and if it might be - clobbered by the statement reset the allocation - statement. */ - ao_ref_init_from_ptr_and_size (&r, si->ptr, NULL_TREE); - if (stmt_may_clobber_ref_p_1 (stmt, &r)) - si->stmt = NULL; - } - } - si->dont_invalidate = false; - nonempty = true; - } + /* Unconditionally reset DONT_INVALIDATE. */ + bool dont_invalidate = si->dont_invalidate; + si->dont_invalidate = false; + + if (dont_invalidate) + continue; + + ao_ref r; + tree size = NULL_TREE; + if (si->nonzero_chars) + { + /* Include the terminating nul in the size of the string + to consider when determining possible clobber. */ + tree type = TREE_TYPE (si->nonzero_chars); + size = fold_build2 (PLUS_EXPR, type, si->nonzero_chars, + build_int_cst (type, 1)); + } + ao_ref_init_from_ptr_and_size (&r, si->ptr, size); + if (stmt_may_clobber_ref_p_1 (stmt, &r)) + { + if (dump_file && (dump_flags & TDF_DETAILS)) + { + fputs (" statement may clobber object ", dump_file); + print_generic_expr (dump_file, si->ptr); + if (size && tree_fits_uhwi_p (size)) + fprintf (dump_file, " " HOST_WIDE_INT_PRINT_UNSIGNED + " bytes in size", tree_to_uhwi (size)); + fputc ('\n', dump_file); + } + + set_strinfo (i, NULL); + free_strinfo (si); + continue; + } + + if (size + && !zero_write + && si->stmt + && is_gimple_call (si->stmt) + && (DECL_FUNCTION_CODE (gimple_call_fndecl (si->stmt)) + == BUILT_IN_CALLOC)) + { + /* If the clobber test above considered the length of + the string (including the nul), then for (potentially) + non-zero writes that might modify storage allocated by + calloc consider the whole object and if it might be + clobbered by the statement reset the statement. */ + ao_ref_init_from_ptr_and_size (&r, si->ptr, NULL_TREE); + if (stmt_may_clobber_ref_p_1 (stmt, &r)) + si->stmt = NULL; + } + } if (dump_file && (dump_flags & TDF_DETAILS)) - fprintf (dump_file, " %s() ==> %i\n", __func__, nonempty); + fprintf (dump_file, "%s returns %i\n", __func__, nonempty); return nonempty; } @@ -1289,6 +1318,7 @@ unshare_strinfo (strinfo *si) nsi = new_strinfo (si->ptr, si->idx, si->nonzero_chars, si->full_string_p); nsi->stmt = si->stmt; + nsi->alloc = si->alloc; nsi->endptr = si->endptr; nsi->first = si->first; nsi->prev = si->prev; @@ -1582,6 +1612,8 @@ valid_builtin_call (gimple *stmt) return false; break; + case BUILT_IN_ALLOCA: + case BUILT_IN_ALLOCA_WITH_ALIGN: case BUILT_IN_CALLOC: case BUILT_IN_MALLOC: case BUILT_IN_MEMCPY: @@ -1858,7 +1890,8 @@ maybe_set_strlen_range (tree lhs, tree src, tree bound) } /* Diagnose buffer overflow by a STMT writing LEN + PLUS_ONE bytes, - into an object designated by the LHS of STMT otherise. */ + either into a region allocated for the object SI when non-null, + or into an object designated by the LHS of STMT otherwise. */ static void maybe_warn_overflow (gimple *stmt, tree len, @@ -1868,82 +1901,144 @@ maybe_warn_overflow (gimple *stmt, tree len, if (!len || gimple_no_warning_p (stmt)) return; + /* The DECL of the function performing the write if it is done + by one. */ tree writefn = NULL_TREE; - tree destdecl = NULL_TREE; - tree destsize = NULL_TREE; + /* The destination expression involved in the store STMT. */ tree dest = NULL_TREE; - /* The offset into the destination object set by compute_objsize - but already reflected in DESTSIZE. */ - tree destoff = NULL_TREE; - if (is_gimple_assign (stmt)) - { - dest = gimple_assign_lhs (stmt); - if (TREE_NO_WARNING (dest)) - return; - - /* For assignments try to determine the size of the destination - first. Set DESTOFF to the the offset on success. */ - tree off = size_zero_node; - destsize = compute_objsize (dest, 1, &destdecl, &off); - if (destsize) - destoff = off; - } + dest = gimple_assign_lhs (stmt); else if (is_gimple_call (stmt)) { - writefn = gimple_call_fndecl (stmt); dest = gimple_call_arg (stmt, 0); + writefn = gimple_call_fndecl (stmt); } + if (TREE_NO_WARNING (dest)) + return; + /* The offset into the destination object computed below and not - reflected in DESTSIZE. Either DESTOFF is set above or OFFRNG - below. */ + reflected in DESTSIZE (computed below). */ wide_int offrng[2]; - offrng[0] = wi::zero (TYPE_PRECISION (sizetype)); - offrng[1] = offrng[0]; + const int off_prec = TYPE_PRECISION (ptrdiff_type_node); + offrng[0] = offrng[1] = wi::zero (off_prec); - if (!destsize && !si && dest) + if (!si) { - /* For both assignments and calls, if no destination STRINFO was - provided, try to get it from the DEST. */ + /* If no destination STRINFO was provided try to get it from + the DEST argument. */ tree ref = dest; - tree off = NULL_TREE; if (TREE_CODE (ref) == ARRAY_REF) { /* Handle stores to VLAs (represented as ARRAY_REF (MEM_REF (vlaptr, 0), N]. */ - off = TREE_OPERAND (ref, 1); + tree off = TREE_OPERAND (ref, 1); ref = TREE_OPERAND (ref, 0); + if (get_range (off, offrng, rvals)) + { + offrng[0] = offrng[0].from (offrng[0], off_prec, SIGNED); + offrng[1] = offrng[1].from (offrng[1], off_prec, SIGNED); + } + else + { + offrng[0] = wi::to_wide (TYPE_MIN_VALUE (ptrdiff_type_node)); + offrng[1] = wi::to_wide (TYPE_MAX_VALUE (ptrdiff_type_node)); + } } if (TREE_CODE (ref) == MEM_REF) { tree mem_off = TREE_OPERAND (ref, 1); - if (off) + ref = TREE_OPERAND (ref, 0); + wide_int memoffrng[2]; + if (get_range (mem_off, memoffrng, rvals)) { - if (!integer_zerop (mem_off)) - return; + offrng[0] += memoffrng[0]; + offrng[1] += memoffrng[1]; } else - off = mem_off; - ref = TREE_OPERAND (ref, 0); + { + offrng[0] = wi::to_wide (TYPE_MIN_VALUE (ptrdiff_type_node)); + offrng[1] = wi::to_wide (TYPE_MAX_VALUE (ptrdiff_type_node)); + } } - if (int idx = get_stridx (ref, offrng)) + wide_int stroffrng[2]; + if (int idx = get_stridx (ref, stroffrng, rvals)) { si = get_strinfo (idx); - if (off && TREE_CODE (off) == INTEGER_CST) + offrng[0] += stroffrng[0]; + offrng[1] += stroffrng[1]; + } + } + + /* The allocation call if the destination object was allocated + by one. */ + gimple *alloc_call = NULL; + /* The DECL of the destination object if known and not dynamically + allocated. */ + tree destdecl = NULL_TREE; + /* The offset into the destination object set by compute_objsize + but already reflected in DESTSIZE. */ + tree destoff = NULL_TREE; + /* The size of the destination region (which is smaller than + the destination object for stores at a non-zero offset). */ + tree destsize = NULL_TREE; + + /* Compute the range of sizes of the destination object. The range + is constant for declared objects but may be a range for allocated + objects. */ + const int siz_prec = TYPE_PRECISION (size_type_node); + wide_int sizrng[2]; + if (si) + { + destsize = gimple_call_alloc_size (si->alloc, sizrng, rvals); + alloc_call = si->alloc; + } + else + offrng[0] = offrng[1] = wi::zero (off_prec); + + if (!destsize) + { + /* If there is no STRINFO for DEST, fall back on compute_objsize. */ + tree off = size_zero_node; + destsize = compute_objsize (dest, 1, &destdecl, &off, rvals); + if (destsize) + { + /* Remember OFF but clear OFFRNG that may have been set above. */ + destoff = off; + offrng[0] = offrng[1] = wi::zero (off_prec); + + if (destdecl && TREE_CODE (destdecl) == SSA_NAME) + { + gimple *stmt = SSA_NAME_DEF_STMT (destdecl); + if (is_gimple_call (stmt)) + alloc_call = stmt; + destdecl = NULL_TREE; + } + + if (!get_range (destsize, sizrng, rvals)) { - wide_int wioff = wi::to_wide (off, offrng->get_precision ()); - offrng[0] += wioff; - offrng[1] += wioff; + /* On failure, rather than failing, set the maximum range + so that overflow in allocated objects whose size depends + on the strlen of the source can still be diagnosed + below. */ + sizrng[0] = wi::zero (siz_prec); + sizrng[1] = wi::to_wide (TYPE_MAX_VALUE (sizetype)); } } - else - return; } + if (!destsize) + { + sizrng[0] = wi::zero (siz_prec); + sizrng[1] = wi::to_wide (TYPE_MAX_VALUE (sizetype)); + }; + + sizrng[0] = sizrng[0].from (sizrng[0], siz_prec, UNSIGNED); + sizrng[1] = sizrng[1].from (sizrng[1], siz_prec, UNSIGNED); + /* Return early if the DESTSIZE size expression is the same as LEN and the offset into the destination is zero. This might happen in the case of a pair of malloc and memset calls to allocate @@ -1961,26 +2056,13 @@ maybe_warn_overflow (gimple *stmt, tree len, lenrng[1] += 1; } - /* Compute the range of sizes of the destination object. The range - is constant for declared objects but may be a range for allocated - objects. */ - wide_int sizrng[2]; - if (!destsize || !get_range (destsize, sizrng, rvals)) - { - /* On failure, rather than bailing outright, use the maximum range - so that overflow in allocated objects whose size depends on - the strlen of the source can still be diagnosed below. */ - sizrng[0] = wi::zero (lenrng->get_precision ()); - sizrng[1] = wi::to_wide (TYPE_MAX_VALUE (ptrdiff_type_node)); - } - - /* The size of the remaining space in the destination computed as + /* The size of the remaining space in the destiation computed as the size of the latter minus the offset into it. */ wide_int spcrng[2] = { sizrng[0], sizrng[1] }; if (wi::sign_mask (offrng[0])) { /* FIXME: Handle negative offsets into allocated objects. */ - if (destdecl) + if (destsize) spcrng[0] = spcrng[1] = wi::zero (spcrng->get_precision ()); else return; @@ -1991,7 +2073,8 @@ maybe_warn_overflow (gimple *stmt, tree len, spcrng[1] -= wi::ltu_p (offrng[0], spcrng[1]) ? offrng[0] : spcrng[1]; } - if (wi::leu_p (lenrng[0], spcrng[0])) + if (wi::leu_p (lenrng[0], spcrng[0]) + && wi::leu_p (lenrng[1], spcrng[1])) return; if (lenrng[0] == spcrng[1] @@ -2092,6 +2175,8 @@ maybe_warn_overflow (gimple *stmt, tree len, if (!warned) return; + gimple_set_no_warning (stmt, true); + /* If DESTOFF is not null, use it to format the offset value/range. */ if (destoff) get_range (destoff, offrng); @@ -2117,6 +2202,62 @@ maybe_warn_overflow (gimple *stmt, tree len, offstr, destdecl); return; } + + if (!alloc_call) + return; + + tree allocfn = gimple_call_fndecl (alloc_call); + + if (gimple_call_builtin_p (alloc_call, BUILT_IN_ALLOCA_WITH_ALIGN)) + { + if (sizrng[0] == sizrng[1]) + inform (gimple_location (alloc_call), + "at offset %s to an object with size %wu declared here", + offstr, sizrng[0].to_uhwi ()); + else if (sizrng[0] == 0) + { + /* Avoid printing impossible sizes. */ + if (wi::ltu_p (sizrng[1], + wi::to_wide (TYPE_MAX_VALUE (ptrdiff_type_node)) - 2)) + inform (gimple_location (alloc_call), + "at offset %s to an object with size at most %wu " + "declared here", + offstr, sizrng[1].to_uhwi ()); + else + inform (gimple_location (alloc_call), + "at offset %s to an object declared here", offstr); + } + else + inform (gimple_location (alloc_call), + "at offset %s to an object with size between %wu and %wu " + "declared here", + offstr, sizrng[0].to_uhwi (), sizrng[1].to_uhwi ()); + return; + } + + if (sizrng[0] == sizrng[1]) + inform (gimple_location (alloc_call), + "at offset %s to an object with size %wu allocated by %qD here", + offstr, sizrng[0].to_uhwi (), allocfn); + else if (sizrng[0] == 0) + { + /* Avoid printing impossible sizes. */ + if (wi::ltu_p (sizrng[1], + wi::to_wide (TYPE_MAX_VALUE (ptrdiff_type_node)) - 2)) + inform (gimple_location (alloc_call), + "at offset %s to an object with size at most %wu allocated " + "by %qD here", + offstr, sizrng[1].to_uhwi (), allocfn); + else + inform (gimple_location (alloc_call), + "at offset %s to an object allocated by %qD here", + offstr, allocfn); + } + else + inform (gimple_location (alloc_call), + "at offset %s to an object with size between %wu and %wu " + "allocated by %qD here", + offstr, sizrng[0].to_uhwi (), sizrng[1].to_uhwi (), allocfn); } /* Convenience wrapper for the above. */ @@ -2243,7 +2384,7 @@ handle_builtin_strlen (gimple_stmt_iterator *gsi) tree old = si->nonzero_chars; si->nonzero_chars = lhs; si->full_string_p = true; - if (TREE_CODE (old) == INTEGER_CST) + if (old && TREE_CODE (old) == INTEGER_CST) { old = fold_convert_loc (loc, TREE_TYPE (lhs), old); tree adj = fold_build2_loc (loc, MINUS_EXPR, @@ -2425,7 +2566,8 @@ handle_builtin_strchr (gimple_stmt_iterator *gsi) memcpy. */ static void -handle_builtin_strcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi) +handle_builtin_strcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi, + const vr_values *rvals) { int idx, didx; tree src, dst, srclen, len, lhs, type, fn, oldlen; @@ -2459,6 +2601,11 @@ handle_builtin_strcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi) else if (idx < 0) srclen = build_int_cst (size_type_node, ~idx); + maybe_warn_overflow (stmt, srclen, rvals, olddsi, true); + + if (olddsi != NULL) + adjust_last_stmt (olddsi, stmt, false); + loc = gimple_location (stmt); if (srclen == NULL_TREE) switch (bcode) @@ -2709,26 +2856,58 @@ is_strlen_related_p (tree src, tree len) if (TREE_CODE (len) != SSA_NAME) return false; - gimple *def_stmt = SSA_NAME_DEF_STMT (len); - if (!def_stmt) + if (TREE_CODE (src) == SSA_NAME) + { + gimple *srcdef = SSA_NAME_DEF_STMT (src); + if (is_gimple_assign (srcdef)) + { + /* Handle bitwise AND used in conversions from wider size_t + to narrower unsigned types. */ + tree_code code = gimple_assign_rhs_code (srcdef); + if (code == BIT_AND_EXPR + || code == NOP_EXPR) + return is_strlen_related_p (gimple_assign_rhs1 (srcdef), len); + + return false; + } + + if (gimple_call_builtin_p (srcdef, BUILT_IN_NORMAL)) + { + /* If SRC is the result of a call to an allocation function + or strlen, use the function's argument instead. */ + tree func = gimple_call_fndecl (srcdef); + built_in_function code = DECL_FUNCTION_CODE (func); + if (code == BUILT_IN_ALLOCA + || code == BUILT_IN_ALLOCA_WITH_ALIGN + || code == BUILT_IN_MALLOC + || code == BUILT_IN_STRLEN) + return is_strlen_related_p (gimple_call_arg (srcdef, 0), len); + + /* FIXME: Handle other functions with attribute alloc_size. */ + return false; + } + } + + gimple *lendef = SSA_NAME_DEF_STMT (len); + if (!lendef) return false; - if (is_gimple_call (def_stmt)) + if (is_gimple_call (lendef)) { - tree func = gimple_call_fndecl (def_stmt); - if (!valid_builtin_call (def_stmt) + tree func = gimple_call_fndecl (lendef); + if (!valid_builtin_call (lendef) || DECL_FUNCTION_CODE (func) != BUILT_IN_STRLEN) return false; - tree arg = gimple_call_arg (def_stmt, 0); + tree arg = gimple_call_arg (lendef, 0); return is_strlen_related_p (src, arg); } - if (!is_gimple_assign (def_stmt)) + if (!is_gimple_assign (lendef)) return false; - tree_code code = gimple_assign_rhs_code (def_stmt); - tree rhs1 = gimple_assign_rhs1 (def_stmt); + tree_code code = gimple_assign_rhs_code (lendef); + tree rhs1 = gimple_assign_rhs1 (lendef); tree rhstype = TREE_TYPE (rhs1); if ((TREE_CODE (rhstype) == POINTER_TYPE && code == POINTER_PLUS_EXPR) @@ -2741,7 +2920,7 @@ is_strlen_related_p (tree src, tree len) return is_strlen_related_p (src, rhs1); } - if (tree rhs2 = gimple_assign_rhs2 (def_stmt)) + if (tree rhs2 = gimple_assign_rhs2 (lendef)) { /* Integer subtraction is considered strlen-related when both arguments are integers and second one is strlen-related. */ @@ -3190,31 +3369,34 @@ handle_builtin_stxncpy (built_in_function, gimple_stmt_iterator *gsi) call. */ static void -handle_builtin_memcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi) +handle_builtin_memcpy (enum built_in_function bcode, gimple_stmt_iterator *gsi, + const vr_values *rvals) { - int idx, didx; - tree src, dst, len, lhs, oldlen, newlen; + tree lhs, oldlen, newlen; gimple *stmt = gsi_stmt (*gsi); - strinfo *si, *dsi, *olddsi; + strinfo *si, *dsi; - len = gimple_call_arg (stmt, 2); - src = gimple_call_arg (stmt, 1); - dst = gimple_call_arg (stmt, 0); - idx = get_stridx (src); - if (idx == 0) - return; + tree len = gimple_call_arg (stmt, 2); + tree src = gimple_call_arg (stmt, 1); + tree dst = gimple_call_arg (stmt, 0); - didx = get_stridx (dst); - olddsi = NULL; + int didx = get_stridx (dst); + strinfo *olddsi = NULL; if (didx > 0) olddsi = get_strinfo (didx); else if (didx < 0) return; if (olddsi != NULL - && tree_fits_uhwi_p (len) && !integer_zerop (len)) - adjust_last_stmt (olddsi, stmt, false); + { + maybe_warn_overflow (stmt, len, rvals, olddsi); + adjust_last_stmt (olddsi, stmt, false); + } + + int idx = get_stridx (src); + if (idx == 0) + return; bool full_string_p; if (idx > 0) @@ -3611,10 +3793,11 @@ handle_builtin_strcat (enum built_in_function bcode, gimple_stmt_iterator *gsi) gimple_set_no_warning (stmt, true); } -/* Handle a call to malloc or calloc. */ +/* Handle a call to an allocation function like alloca, malloc or calloc, + or an ordinary allocation function declared with attribute alloc_size. */ static void -handle_builtin_malloc (enum built_in_function bcode, gimple_stmt_iterator *gsi) +handle_alloc_call (enum built_in_function bcode, gimple_stmt_iterator *gsi) { gimple *stmt = gsi_stmt (*gsi); tree lhs = gimple_call_lhs (stmt); @@ -3628,10 +3811,19 @@ handle_builtin_malloc (enum built_in_function bcode, gimple_stmt_iterator *gsi) length = build_int_cst (size_type_node, 0); strinfo *si = new_strinfo (lhs, idx, length, length != NULL_TREE); if (bcode == BUILT_IN_CALLOC) - si->endptr = lhs; + { + /* Only set STMT for calloc and malloc. */ + si->stmt = stmt; + /* Only set ENDPTR for calloc. */ + si->endptr = lhs; + } + else if (bcode == BUILT_IN_MALLOC) + si->stmt = stmt; + + /* Set ALLOC is set for all allocation functions. */ + si->alloc = stmt; set_strinfo (idx, si); si->writable = true; - si->stmt = stmt; si->dont_invalidate = true; } @@ -3641,46 +3833,66 @@ handle_builtin_malloc (enum built_in_function bcode, gimple_stmt_iterator *gsi) return true when the call is transformed, false otherwise. */ static bool -handle_builtin_memset (gimple_stmt_iterator *gsi, bool *zero_write) +handle_builtin_memset (gimple_stmt_iterator *gsi, bool *zero_write, + const vr_values *rvals) { - gimple *stmt2 = gsi_stmt (*gsi); - if (!integer_zerop (gimple_call_arg (stmt2, 1))) - return false; - - /* Let the caller know the memset call cleared the destination. */ - *zero_write = true; - - tree ptr = gimple_call_arg (stmt2, 0); - int idx1 = get_stridx (ptr); + gimple *memset_stmt = gsi_stmt (*gsi); + tree ptr = gimple_call_arg (memset_stmt, 0); + /* Set to the non-constant offset added to PTR. */ + wide_int offrng[2]; + int idx1 = get_stridx (ptr, offrng, rvals); if (idx1 <= 0) return false; strinfo *si1 = get_strinfo (idx1); if (!si1) return false; - gimple *stmt1 = si1->stmt; - if (!stmt1 || !is_gimple_call (stmt1)) + gimple *alloc_stmt = si1->alloc; + if (!alloc_stmt || !is_gimple_call (alloc_stmt)) return false; - tree callee1 = gimple_call_fndecl (stmt1); - if (!valid_builtin_call (stmt1)) + tree callee1 = gimple_call_fndecl (alloc_stmt); + if (!valid_builtin_call (alloc_stmt)) return false; + tree alloc_size = gimple_call_arg (alloc_stmt, 0); + tree memset_size = gimple_call_arg (memset_stmt, 2); + + /* Check for overflow. */ + maybe_warn_overflow (memset_stmt, memset_size, rvals); + + /* Bail when there is no statement associated with the destination + (the statement may be null even when SI1->ALLOC is not). */ + if (!si1->stmt) + return false; + + /* Avoid optimizing if store is at a variable offset from the beginning + of the allocated object. */ + if (offrng[0] != 0 || offrng[0] != offrng[1]) + return false; + + /* Bail when the call writes a non-zero value. */ + if (!integer_zerop (gimple_call_arg (memset_stmt, 1))) + return false; + + /* Let the caller know the memset call cleared the destination. */ + *zero_write = true; + enum built_in_function code1 = DECL_FUNCTION_CODE (callee1); - tree size = gimple_call_arg (stmt2, 2); if (code1 == BUILT_IN_CALLOC) - /* Not touching stmt1 */ ; + /* Not touching alloc_stmt */ ; else if (code1 == BUILT_IN_MALLOC - && operand_equal_p (gimple_call_arg (stmt1, 0), size, 0)) + && operand_equal_p (memset_size, alloc_size, 0)) { - gimple_stmt_iterator gsi1 = gsi_for_stmt (stmt1); + /* Replace the malloc + memset calls with calloc. */ + gimple_stmt_iterator gsi1 = gsi_for_stmt (si1->stmt); update_gimple_call (&gsi1, builtin_decl_implicit (BUILT_IN_CALLOC), 2, - size, build_one_cst (size_type_node)); + alloc_size, build_one_cst (size_type_node)); si1->nonzero_chars = build_int_cst (size_type_node, 0); si1->full_string_p = true; si1->stmt = gsi_stmt (gsi1); } else return false; - tree lhs = gimple_call_lhs (stmt2); - unlink_stmt_vdef (stmt2); + tree lhs = gimple_call_lhs (memset_stmt); + unlink_stmt_vdef (memset_stmt); if (lhs) { gimple *assign = gimple_build_assign (lhs, ptr); @@ -3689,7 +3901,7 @@ handle_builtin_memset (gimple_stmt_iterator *gsi, bool *zero_write) else { gsi_remove (gsi, true); - release_defs (stmt2); + release_defs (memset_stmt); } return true; @@ -4438,6 +4650,29 @@ count_nonzero_bytes (tree exp, unsigned HOST_WIDE_INT offset, if (maxlen + 1 < nbytes) return false; + if (!nbytes + && TREE_CODE (si->ptr) == SSA_NAME + && !POINTER_TYPE_P (TREE_TYPE (si->ptr))) + { + /* SI->PTR is an SSA_NAME with a DEF_STMT like + _1 = MEM [(char * {ref-all})s_4(D)]; */ + gimple *stmt = SSA_NAME_DEF_STMT (exp); + if (gimple_assign_single_p (stmt) + && gimple_assign_rhs_code (stmt) == MEM_REF) + { + tree rhs = gimple_assign_rhs1 (stmt); + if (tree refsize = TYPE_SIZE_UNIT (TREE_TYPE (rhs))) + if (tree_fits_uhwi_p (refsize)) + { + nbytes = tree_to_uhwi (refsize); + maxlen = nbytes; + } + } + + if (!nbytes) + return false; + } + if (nbytes <= minlen) *nulterm = false; @@ -4454,7 +4689,7 @@ count_nonzero_bytes (tree exp, unsigned HOST_WIDE_INT offset, lenrange[1] = maxlen; if (lenrange[2] < nbytes) - (lenrange[2] = nbytes); + lenrange[2] = nbytes; /* Since only the length of the string are known and not its contents, clear ALLNUL and ALLNONNUL purely on the basis of the length. */ @@ -4672,7 +4907,8 @@ count_nonzero_bytes (tree exp, unsigned lenrange[3], bool *nulterm, the next statement in the basic block and false otherwise. */ static bool -handle_store (gimple_stmt_iterator *gsi, bool *zero_write, const vr_values *rvals) +handle_store (gimple_stmt_iterator *gsi, bool *zero_write, + const vr_values *rvals) { int idx = -1; strinfo *si = NULL; @@ -5080,12 +5316,18 @@ is_char_type (tree type) in the basic block and false otherwise. */ static bool -strlen_check_and_optimize_call (gimple_stmt_iterator *gsi, - bool *zero_write, +strlen_check_and_optimize_call (gimple_stmt_iterator *gsi, bool *zero_write, const vr_values *rvals) { gimple *stmt = gsi_stmt (*gsi); + if (!gimple_call_builtin_p (stmt, BUILT_IN_NORMAL)) + { + tree fntype = gimple_call_fntype (stmt); + if (fntype && lookup_attribute ("alloc_size", TYPE_ATTRIBUTES (fntype))) + handle_alloc_call (BUILT_IN_NONE, gsi); + } + /* When not optimizing we must be checking printf calls which we do even for user-defined functions when they are declared with attribute format. */ @@ -5108,7 +5350,7 @@ strlen_check_and_optimize_call (gimple_stmt_iterator *gsi, case BUILT_IN_STRCPY_CHK: case BUILT_IN_STPCPY: case BUILT_IN_STPCPY_CHK: - handle_builtin_strcpy (DECL_FUNCTION_CODE (callee), gsi); + handle_builtin_strcpy (DECL_FUNCTION_CODE (callee), gsi, rvals); break; case BUILT_IN_STRNCAT: @@ -5127,18 +5369,20 @@ strlen_check_and_optimize_call (gimple_stmt_iterator *gsi, case BUILT_IN_MEMCPY_CHK: case BUILT_IN_MEMPCPY: case BUILT_IN_MEMPCPY_CHK: - handle_builtin_memcpy (DECL_FUNCTION_CODE (callee), gsi); + handle_builtin_memcpy (DECL_FUNCTION_CODE (callee), gsi, rvals); break; case BUILT_IN_STRCAT: case BUILT_IN_STRCAT_CHK: handle_builtin_strcat (DECL_FUNCTION_CODE (callee), gsi); break; + case BUILT_IN_ALLOCA: + case BUILT_IN_ALLOCA_WITH_ALIGN: case BUILT_IN_MALLOC: case BUILT_IN_CALLOC: - handle_builtin_malloc (DECL_FUNCTION_CODE (callee), gsi); + handle_alloc_call (DECL_FUNCTION_CODE (callee), gsi); break; case BUILT_IN_MEMSET: - if (handle_builtin_memset (gsi, zero_write)) + if (handle_builtin_memset (gsi, zero_write, rvals)) return false; break; case BUILT_IN_MEMCMP: @@ -5163,7 +5407,8 @@ strlen_check_and_optimize_call (gimple_stmt_iterator *gsi, If GSI's basic block needs clean-up of EH, set *CLEANUP_EH to true. */ static void -handle_integral_assign (gimple_stmt_iterator *gsi, bool *cleanup_eh) +handle_integral_assign (gimple_stmt_iterator *gsi, bool *cleanup_eh, + const vr_values *rvals) { gimple *stmt = gsi_stmt (*gsi); tree lhs = gimple_assign_lhs (stmt); @@ -5266,6 +5511,31 @@ handle_integral_assign (gimple_stmt_iterator *gsi, bool *cleanup_eh) } } } + else if (code == MEM_REF && TREE_CODE (lhs) == SSA_NAME) + { + if (int idx = new_stridx (lhs)) + { + /* Record multi-byte assignments from MEM_REFs. */ + bool storing_all_nonzero_p; + bool storing_all_zeros_p; + bool full_string_p; + unsigned lenrange[] = { UINT_MAX, 0, 0 }; + tree rhs = gimple_assign_rhs1 (stmt); + const bool ranges_valid + = count_nonzero_bytes (rhs, lenrange, &full_string_p, + &storing_all_zeros_p, &storing_all_nonzero_p, + rvals); + if (ranges_valid) + { + tree length = build_int_cst (sizetype, lenrange[0]); + strinfo *si = new_strinfo (lhs, idx, length, full_string_p); + set_strinfo (idx, si); + si->writable = true; + si->dont_invalidate = true; + maybe_warn_overflow (stmt, lenrange[2], rvals); + } + } + } if (strlen_to_stridx) { @@ -5318,29 +5588,35 @@ check_and_optimize_stmt (gimple_stmt_iterator *gsi, bool *cleanup_eh, } else if (TREE_CODE (lhs) == SSA_NAME && INTEGRAL_TYPE_P (lhs_type)) /* Handle assignment to a character. */ - handle_integral_assign (gsi, cleanup_eh); + handle_integral_assign (gsi, cleanup_eh, rvals); else if (TREE_CODE (lhs) != SSA_NAME && !TREE_SIDE_EFFECTS (lhs)) { tree type = TREE_TYPE (lhs); if (TREE_CODE (type) == ARRAY_TYPE) type = TREE_TYPE (type); - bool is_char_store = is_char_type (type); - if (!is_char_store && TREE_CODE (lhs) == MEM_REF) - { - /* To consider stores into char objects via integer types - other than char but not those to non-character objects, - determine the type of the destination rather than just - the type of the access. */ - tree ref = TREE_OPERAND (lhs, 0); - type = TREE_TYPE (ref); - if (TREE_CODE (type) == POINTER_TYPE) - type = TREE_TYPE (type); - if (TREE_CODE (type) == ARRAY_TYPE) - type = TREE_TYPE (type); - if (is_char_type (type)) - is_char_store = true; - } + bool is_char_store = is_char_type (type); + if (!is_char_store && TREE_CODE (lhs) == MEM_REF) + { + /* To consider stores into char objects via integer types + other than char but not those to non-character objects, + determine the type of the destination rather than just + the type of the access. */ + for (int i = 0; i != 2; ++i) + { + tree ref = TREE_OPERAND (lhs, i); + type = TREE_TYPE (ref); + if (TREE_CODE (type) == POINTER_TYPE) + type = TREE_TYPE (type); + if (TREE_CODE (type) == ARRAY_TYPE) + type = TREE_TYPE (type); + if (is_char_type (type)) + { + is_char_store = true; + break; + } + } + } /* Handle a single or multibyte assignment. */ if (is_char_store && !handle_store (gsi, &zero_write, rvals))