From patchwork Tue Nov 30 22:55:04 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Martin Sebor X-Patchwork-Id: 48312 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id E5E703857818 for ; Tue, 30 Nov 2021 22:56:17 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org E5E703857818 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gcc.gnu.org; s=default; t=1638312978; bh=LcaAEjiHG/AFCH/Xc1GHaWjt5o3yHQ4VmdbnyNcsLeo=; h=Subject:To:References:Date:In-Reply-To:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=uck+LoqHk3Gc/TF/ySKW0HiAQNr/Hk6q0edN9a6ytHAxNXT1QYEdkTzrHaR97xD1U lJXEkf8RnepmIygVjnTGele2tPBO/1S1zGkaxufjGzoj0m1ME+1O2FSGytSwZCR6ry OzNTPdE4B6uTacEisS28v8ZTX4WJiZkYuG3l0SYQ= X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from mail-oi1-x22a.google.com (mail-oi1-x22a.google.com [IPv6:2607:f8b0:4864:20::22a]) by sourceware.org (Postfix) with ESMTPS id 53325385BF9B for ; Tue, 30 Nov 2021 22:55:07 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org 53325385BF9B Received: by mail-oi1-x22a.google.com with SMTP id q25so44500084oiw.0 for ; Tue, 30 Nov 2021 14:55:07 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:subject:to:references:from:message-id:date :user-agent:mime-version:in-reply-to:content-language; bh=LcaAEjiHG/AFCH/Xc1GHaWjt5o3yHQ4VmdbnyNcsLeo=; b=EYgnnpBy+D0UAA1aPRxNceD4Faf9+VgtVkHi5PCriSvPUVdTMagN1Sp6PhqFTuUIuY Af88tzQK251ux9w4AnpFa4+U9uWS8cGs2kAdpkReoBZwOi1m9p4kDjgUd3V214/c3dck LQ+7+0k7pNsb27LzmCNeyihtDcB0FqME48F/OFroC5iy/J9FudswWd58oNRdzD8iJqwo P8wn91LQMqAwve2z/QC5Koo5bWNwCGOQvfYdFcX/xy5bDQI/MpC1232lt1FaJJKD5bGf QgOVfUOo6Sx8yUur3QLVSudVPnMeLvcqf80xcdVuFCZXNVpRQi2RxwiRiHYJXrIrMWYA osFg== X-Gm-Message-State: AOAM530aeRFMwewS01W+hdXyHIXvFNmJzvOC6k0RwFd8o8CFiGjjDxLp kx+MeX6cdopt2PBFvD9l9TODopiDxAE= X-Google-Smtp-Source: ABdhPJwyuYj3535sXfWBfT6YWkm3hQKaErG3U/v3VGs18956MN7Z0KuRMPQMdZ/GH+SGB/aAqCvllg== X-Received: by 2002:a05:6808:2186:: with SMTP id be6mr2131808oib.115.1638312906506; Tue, 30 Nov 2021 14:55:06 -0800 (PST) Received: from [192.168.0.41] (184-96-227-137.hlrn.qwest.net. [184.96.227.137]) by smtp.gmail.com with ESMTPSA id bn41sm3990213oib.18.2021.11.30.14.55.05 for (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Tue, 30 Nov 2021 14:55:06 -0800 (PST) Subject: [PATCH v2 2/2] add -Wdangling-pointer [PR #63272] To: gcc-patches References: <004d5f0a-a92c-31be-c7b5-31f21f853139@redhat.com> Message-ID: Date: Tue, 30 Nov 2021 15:55:04 -0700 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Thunderbird/68.2.2 MIME-Version: 1.0 In-Reply-To: <004d5f0a-a92c-31be-c7b5-31f21f853139@redhat.com> Content-Language: en-US X-Spam-Status: No, score=-10.5 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, FREEMAIL_FROM, GIT_PATCH_0, RCVD_IN_DNSWL_NONE, SPF_HELO_NONE, SPF_PASS, TXREP autolearn=ham autolearn_force=no version=3.4.4 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on server2.sourceware.org X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Martin Sebor via Gcc-patches From: Martin Sebor Reply-To: Martin Sebor Errors-To: gcc-patches-bounces+patchwork=sourceware.org@gcc.gnu.org Sender: "Gcc-patches" Attached is a revision of this patch with adjustments for the changes to the prerequisite patch 1 in the series and a couple of minor simplifications and slightly improved test coverage, rested on x86_64-linux. On 11/1/21 4:18 PM, Martin Sebor wrote: > Patch 2 in this series adds support for detecting the uses of > dangling pointers: those to auto objects that have gone out of > scope.  Like patch 1, to minimize false positives this detection > is very simplistic.  However, thanks to the more deterministic > nature of the problem (all local objects go out of scope) is able > to detect more instances of it.  The approach I used is to simply > search the IL for clobbers that dominate uses of pointers to > the clobbered objects.  If such a use is found that's not > followed by a clobber of the same object the warning triggers. > Similar to -Wuse-after-free, the new -Wdangling-pointer option > has multiple levels: level 1 to detect unconditional uses and > level 2 to flag conditional ones.  Unlike with -Wuse-after-free > there is no use case for testing dangling pointers for > equality, so there is no level 3. > > Tested on x86_64-linux and  by building Glibc and Binutils/GDB. > It found no problems outside of the GCC test suite. > > As with the first patch in this series, the tests contain a number > of xfails due to known limitations marked with pr??????.  I'll > open bugs for them before committing the patch if I don't resolve > them first in a followup. > > Martin Add -Wdangling-pointer [PR63272]. Resolves: PR c/63272 - GCC should warn when using pointer to dead scoped variable within the same function gcc/c-family/ChangeLog: PR c/63272 * c.opt (-Wdangling-pointer): New option. gcc/ChangeLog: PR c/63272 * diagnostic-spec.c (nowarn_spec_t::nowarn_spec_t): Handle -Wdangling-pointer. * doc/invoke.texi (-Wdangling-pointer): Document new option. * gimple-ssa-isolate-paths.c (diag_returned_locals): Suppress warning after issuing it. * gimple-ssa-warn-access.cc (pass_waccess::clone): Set new member. (pass_waccess::check_pointer_uses): New function. (pass_waccess::gimple_call_return_arg): New function. (pass_waccess::gimple_call_return_arg_ref): New function. (pass_waccess::check_call_dangling): New function. (pass_waccess::check_dangling_uses): New function overloads. (pass_waccess::check_dangling_stores): New function. (pass_waccess::check_dangling_stores): New function. (pass_waccess::m_clobbers): New data member. (pass_waccess::m_func): New data member. (pass_waccess::m_run_number): New data member. (pass_waccess::m_check_dangling_p): New data member. (pass_waccess::check_alloca): Check m_early_checks_p. (pass_waccess::check_alloc_size_call): Same. (pass_waccess::check_strcat): Same. (pass_waccess::check_strncat): Same. (pass_waccess::check_stxcpy): Same. (pass_waccess::check_stxncpy): Same. (pass_waccess::check_strncmp): Same. (pass_waccess::check_memop_access): Same. (pass_waccess::check_read_access): Same. (pass_waccess::check_builtin): Call check_pointer_uses. (pass_waccess::warn_invalid_pointer): Add arguments. (is_auto_decl): New function. (pass_waccess::check_stmt): New function. (pass_waccess::check_block): Call check_stmt. (pass_waccess::execute): Call check_dangling_uses, check_dangling_stores. Empty m_clobbers. * passes.def (pass_warn_access): Invoke pass two more times. gcc/testsuite/ChangeLog: PR c/63272 * g++.dg/warn/Wfree-nonheap-object-6.C: Disable valid warnings. * gcc.dg/uninit-pr50476.c: Expect a new warning. * c-c++-common/Wdangling-pointer-2.c: New test. * c-c++-common/Wdangling-pointer-3.c: New test. * c-c++-common/Wdangling-pointer-4.c: New test. * c-c++-common/Wdangling-pointer-5.c: New test. * c-c++-common/Wdangling-pointer.c: New test. * gcc.dg/Wdangling-pointer-2.c: New test. * gcc.dg/Wdangling-pointer.c: New test. diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt index fb1abc0de4c..2e978ae9071 100644 --- a/gcc/c-family/c.opt +++ b/gcc/c-family/c.opt @@ -548,6 +548,14 @@ Wdangling-else C ObjC C++ ObjC++ Var(warn_dangling_else) Warning LangEnabledBy(C ObjC C++ ObjC++,Wparentheses) Warn about dangling else. +Wdangling-pointer +C ObjC C++ LTO ObjC++ Alias(Wdangling-pointer=, 2, 0) Warning +Warn for uses of pointers to auto variables whose lifetime has ended. + +Wdangling-pointer= +C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_dangling_pointer) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall, 2, 0) IntegerRange(0, 2) +Warn for uses of pointers to auto variables whose lifetime has ended. + Wdate-time C ObjC C++ ObjC++ CPP(warn_date_time) CppReason(CPP_W_DATE_TIME) Var(cpp_warn_date_time) Init(0) Warning Warn about __TIME__, __DATE__ and __TIMESTAMP__ usage. diff --git a/gcc/diagnostic-spec.c b/gcc/diagnostic-spec.c index 921e7ab7423..3b1e37a6836 100644 --- a/gcc/diagnostic-spec.c +++ b/gcc/diagnostic-spec.c @@ -99,6 +99,7 @@ nowarn_spec_t::nowarn_spec_t (opt_code opt) m_bits = NW_UNINIT; break; + case OPT_Wdangling_pointer_: case OPT_Wreturn_local_addr: case OPT_Wuse_after_free_: m_bits = NW_DANGLING; diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 46bc8046436..dc44eadb36a 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -341,7 +341,8 @@ Objective-C and Objective-C++ Dialects}. -Wchar-subscripts @gol -Wclobbered -Wcomment @gol -Wconversion -Wno-coverage-mismatch -Wno-cpp @gol --Wdangling-else -Wdate-time @gol +-Wdangling-else -Wdangling-pointer -Wdangling-pointer=@var{n} @gol +-Wdate-time @gol -Wno-deprecated -Wno-deprecated-declarations -Wno-designated-init @gol -Wdisabled-optimization @gol -Wno-discarded-array-qualifiers -Wno-discarded-qualifiers @gol @@ -5691,6 +5692,7 @@ Options} and @ref{Objective-C and Objective-C++ Dialect Options}. -Wcatch-value @r{(C++ and Objective-C++ only)} @gol -Wchar-subscripts @gol -Wcomment @gol +-Wdangling-pointer=2 @gol -Wduplicate-decl-specifier @r{(C and Objective-C only)} @gol -Wenum-compare @r{(in C/ObjC; this is on by default in C++)} @gol -Wformat @gol @@ -8564,6 +8566,50 @@ looks like this: This warning is enabled by @option{-Wparentheses}. +@item -Wdangling-pointer +@itemx -Wdangling-pointer=@var{n} +@opindex Wdangling-pointer +@opindex Wno-dangling-pointer +Warn about uses of pointers to objects with automatic storage duration after +their lifetime has ended. This includes local variables declared in nested +blocks and compound literals. + +@table @gcctabopt +@item -Wdangling-pointer=1 +At level 1 the warning diagnoses only unconditional uses of dangling pointers. +For example +@smallexample +int f (int c1, int c2, x) +@{ + char *p = strchr ((char[])@{ c1, c2 @}, c3); + return p ? *p : 'x'; // warning: dangling pointer to a compound literal +@} +@end smallexample + +@item -Wdangling-pointer=2 +At level 2, in addition to unconditional uses the warning also diagnoses +conditional uses of dangling pointers. + +For example, because the array @var{a} in the following function is out of +scope when the pointer @var{s} that was set to point is used, the warning +triggers at this level. + +@smallexample +void f (char *s) +@{ + if (!s) + @{ + char a[12] = "tmpname"; + s = a; + @} + strcat (s, ".tmp"); // warning: dangling pointer to a may be used + ... +@} +@end smallexample +@end table + +@option{-Wdangling-pointer=2} is included in @option{-Wall}. + @item -Wdate-time @opindex Wdate-time @opindex Wno-date-time diff --git a/gcc/gimple-ssa-warn-access.cc b/gcc/gimple-ssa-warn-access.cc index e396266088f..7ea34df86f6 100644 --- a/gcc/gimple-ssa-warn-access.cc +++ b/gcc/gimple-ssa-warn-access.cc @@ -2066,10 +2066,12 @@ class pass_waccess : public gimple_opt_pass ~pass_waccess (); - opt_pass *clone () { return new pass_waccess (m_ctxt); } + opt_pass *clone (); virtual bool gate (function *); + void set_pass_param (unsigned, bool); + virtual unsigned int execute (function *); private: @@ -2086,6 +2088,9 @@ private: /* Check a call to an ordinary function for invalid accesses. */ bool check_call_access (gcall *); + /* Check a non-call statement. */ + void check_stmt (gimple *); + /* Check statements in a basic block. */ void check_block (basic_block); @@ -2107,26 +2112,41 @@ private: void maybe_check_access_sizes (rdwr_map *, tree, tree, gimple *); /* Check for uses of indeterminate pointers. */ - void check_pointer_uses (gimple *, tree); + void check_pointer_uses (gimple *, tree, tree = NULL_TREE, bool = false); /* Return the argument that a call returns. */ tree gimple_call_return_arg (gcall *); + tree gimple_call_return_arg_ref (gcall *); + + /* Check a call for uses of a dangling pointer arguments. */ + void check_call_dangling (gcall *); + + /* Check uses of a dangling pointer or those derived from it. */ + void check_dangling_uses (tree, tree, bool = false); + void check_dangling_uses (); + void check_dangling_stores (); + void check_dangling_stores (basic_block, hash_set &, auto_bitmap &); - void warn_invalid_pointer (tree, gimple *, gimple *, bool, bool = false); + void warn_invalid_pointer (tree, gimple *, gimple *, tree, bool, bool = false); /* Return true if use follows an invalidating statement. */ - bool use_after_inval_p (gimple *, gimple *); + bool use_after_inval_p (gimple *, gimple *, bool = false); /* A pointer_query object and its cache to store information about pointers and their targets in. */ pointer_query m_ptr_qry; pointer_query::cache_type m_var_cache; - + /* Mapping from DECLs and their clobber statements in the function. */ + hash_map m_clobbers; /* A bit is set for each basic block whose statements have been assigned valid UIDs. */ bitmap m_bb_uids_set; /* The current function. */ function *m_func; + /* True to run checks for uses of dangling pointers. */ + bool m_check_dangling_p; + /* True to run checks early on in the optimization pipeline. */ + bool m_early_checks_p; }; /* Construct the pass. */ @@ -2135,9 +2155,20 @@ pass_waccess::pass_waccess (gcc::context *ctxt) : gimple_opt_pass (pass_data_waccess, ctxt), m_ptr_qry (NULL, &m_var_cache), m_var_cache (), + m_clobbers (), m_bb_uids_set (), - m_func () + m_func (), + m_check_dangling_p (), + m_early_checks_p () +{ +} + +/* Return a copy of the pass with RUN_NUMBER one greater than THIS. */ + +opt_pass* +pass_waccess::clone () { + return new pass_waccess (m_ctxt); } /* Release pointer_query cache. */ @@ -2147,6 +2178,14 @@ pass_waccess::~pass_waccess () m_ptr_qry.flush_cache (); } +void +pass_waccess::set_pass_param (unsigned int n, bool early) +{ + gcc_assert (n == 0); + + m_early_checks_p = early; +} + /* Return true when any checks performed by the pass are enabled. */ bool @@ -2335,6 +2374,9 @@ maybe_warn_alloc_args_overflow (gimple *stmt, const tree args[2], void pass_waccess::check_alloca (gcall *stmt) { + if (m_early_checks_p) + return; + if ((warn_vla_limit >= HOST_WIDE_INT_MAX && warn_alloc_size_limit < warn_vla_limit) || (warn_alloca_limit >= HOST_WIDE_INT_MAX @@ -2356,6 +2398,13 @@ pass_waccess::check_alloca (gcall *stmt) void pass_waccess::check_alloc_size_call (gcall *stmt) { + if (m_early_checks_p) + return; + + if (gimple_call_num_args (stmt) < 1) + /* Avoid invalid calls to functions without a prototype. */ + return; + tree fndecl = gimple_call_fndecl (stmt); if (fndecl && gimple_call_builtin_p (stmt, BUILT_IN_NORMAL)) { @@ -2408,6 +2457,9 @@ pass_waccess::check_alloc_size_call (gcall *stmt) void pass_waccess::check_strcat (gcall *stmt) { + if (m_early_checks_p) + return; + if (!warn_stringop_overflow && !warn_stringop_overread) return; @@ -2433,6 +2485,9 @@ pass_waccess::check_strcat (gcall *stmt) void pass_waccess::check_strncat (gcall *stmt) { + if (m_early_checks_p) + return; + if (!warn_stringop_overflow && !warn_stringop_overread) return; @@ -2502,6 +2557,9 @@ pass_waccess::check_strncat (gcall *stmt) void pass_waccess::check_stxcpy (gcall *stmt) { + if (m_early_checks_p) + return; + tree dst = call_arg (stmt, 0); tree src = call_arg (stmt, 1); @@ -2540,7 +2598,7 @@ pass_waccess::check_stxcpy (gcall *stmt) void pass_waccess::check_stxncpy (gcall *stmt) { - if (!warn_stringop_overflow) + if (m_early_checks_p || !warn_stringop_overflow) return; tree dst = call_arg (stmt, 0); @@ -2564,7 +2622,7 @@ pass_waccess::check_stxncpy (gcall *stmt) void pass_waccess::check_strncmp (gcall *stmt) { - if (!warn_stringop_overread) + if (m_early_checks_p || !warn_stringop_overread) return; tree arg1 = call_arg (stmt, 0); @@ -2669,6 +2727,9 @@ pass_waccess::check_strncmp (gcall *stmt) void pass_waccess::check_memop_access (gimple *stmt, tree dest, tree src, tree size) { + if (m_early_checks_p) + return; + /* For functions like memset and memcpy that operate on raw memory try to determine the size of the largest source and destination object using type-0 Object Size regardless of the object size @@ -2690,7 +2751,7 @@ pass_waccess::check_read_access (gimple *stmt, tree src, tree bound /* = NULL_TREE */, int ost /* = 1 */) { - if (!warn_stringop_overread) + if (m_early_checks_p || !warn_stringop_overread) return; if (bound && !useless_type_conversion_p (size_type_node, TREE_TYPE (bound))) @@ -2818,11 +2879,12 @@ pass_waccess::check_builtin (gcall *stmt) case BUILT_IN_FREE: case BUILT_IN_REALLOC: - { - tree arg = call_arg (stmt, 0); - if (TREE_CODE (arg) == SSA_NAME) - check_pointer_uses (stmt, arg); - } + if (!m_early_checks_p) + { + tree arg = call_arg (stmt, 0); + if (TREE_CODE (arg) == SSA_NAME) + check_pointer_uses (stmt, arg); + } return true; case BUILT_IN_GETTEXT: @@ -3445,16 +3507,64 @@ pass_waccess::maybe_check_dealloc_call (gcall *call) /* Return true if either USE_STMT's basic block (that of a pointer's use) is dominated by INVAL_STMT's (that of a pointer's invalidating statement, - or if they're in the same block, USE_STMT follows INVAL_STMT. */ + which is either a clobber or a deallocation call), or if they're in + the same block, USE_STMT follows INVAL_STMT. */ bool -pass_waccess::use_after_inval_p (gimple *inval_stmt, gimple *use_stmt) +pass_waccess::use_after_inval_p (gimple *inval_stmt, gimple *use_stmt, + bool last_block /* = false */) { + tree clobvar = + gimple_clobber_p (inval_stmt) ? gimple_assign_lhs (inval_stmt) : NULL_TREE; + basic_block inval_bb = gimple_bb (inval_stmt); basic_block use_bb = gimple_bb (use_stmt); if (inval_bb != use_bb) - return dominated_by_p (CDI_DOMINATORS, use_bb, inval_bb); + { + if (dominated_by_p (CDI_DOMINATORS, use_bb, inval_bb)) + return true; + + if (!clobvar || !last_block) + return false; + + /* Proceed only when looking for uses of dangling pointers. */ + auto gsi = gsi_for_stmt (use_stmt); + + auto_bitmap visited; + + /* A use statement in the last basic block in a function or one that + falls through to it is after any other prior clobber of the used + variable unless it's followed by a clobber of the same variable. */ + basic_block bb = use_bb; + while (bb != inval_bb + && single_succ_p (bb) + && !(single_succ_edge (bb)->flags & (EDGE_EH|EDGE_DFS_BACK))) + { + if (!bitmap_set_bit (visited, bb->index)) + /* Avoid cycles. */ + return true; + + for (; !gsi_end_p (gsi); gsi_next_nondebug (&gsi)) + { + gimple *stmt = gsi_stmt (gsi); + if (gimple_clobber_p (stmt)) + { + if (clobvar == gimple_assign_lhs (stmt)) + /* The use is followed by a clobber. */ + return false; + } + } + + bb = single_succ (bb); + gsi = gsi_start_bb (bb); + } + + /* The use is one of a dangling pointer if a clobber of the variable + [the pointer points to] has not been found before the function exit + point. */ + return bb == EXIT_BLOCK_PTR_FOR_FN (cfun); + } if (bitmap_set_bit (m_bb_uids_set, inval_bb->index)) /* The first time this basic block is visited assign increasing ids @@ -3474,12 +3584,15 @@ pass_waccess::use_after_inval_p (gimple *inval_stmt, gimple *use_stmt) /* Issue a warning for the USE_STMT of pointer PTR rendered invalid by INVAL_STMT. PTR may be null when it's been optimized away. - MAYBE is true to issue the "maybe" kind of warning. EQUALITY is - true when the pointer is used in an equality expression. */ + When nonnull, CALLEE is the deallocation function that rendered + the pointer dangling. Otherwise, VAR is the auto variable or + compound literal whose lifetime's rended it dangling. MAYBE is + true to issue the "maybe" kind of warning. EQUALITY is true when + the pointer is used in an equality expression. */ void pass_waccess::warn_invalid_pointer (tree ptr, gimple *use_stmt, - gimple *inval_stmt, + gimple *inval_stmt, tree var, bool maybe, bool equality /* = false */) { @@ -3491,7 +3604,7 @@ pass_waccess::warn_invalid_pointer (tree ptr, gimple *use_stmt, location_t use_loc = gimple_location (use_stmt); if (use_loc == UNKNOWN_LOCATION) { - use_loc = cfun->function_end_locus; + use_loc = m_func->function_end_locus; if (!ptr) /* Avoid issuing a warning with no context other than the function. That would make it difficult to debug @@ -3525,6 +3638,52 @@ pass_waccess::warn_invalid_pointer (tree ptr, gimple *use_stmt, } return; } + + if ((maybe && warn_dangling_pointer < 2) + || warning_suppressed_p (use_stmt, OPT_Wdangling_pointer_)) + return; + + if (DECL_NAME (var)) + { + if ((ptr + && warning_at (use_loc, OPT_Wdangling_pointer_, + (maybe + ? G_("dangling pointer %qE to %qD may be used") + : G_("using dangling pointer %qE to %qD")), + ptr, var)) + || (!ptr + && warning_at (use_loc, OPT_Wdangling_pointer_, + (maybe + ? G_("dangling pointer to %qD may be used") + : G_("using a dangling pointer to %qD")), + var))) + inform (DECL_SOURCE_LOCATION (var), + "%qD declared here", var); + suppress_warning (use_stmt, OPT_Wdangling_pointer_); + return; + } + + if ((ptr + && warning_at (use_loc, OPT_Wdangling_pointer_, + (maybe + ? G_("dangling pointer %qE to a compound literal " + "may be used") + : G_("using dangling pointer %qE to a compound " + "literal")), + ptr, var)) + || (!ptr + && warning_at (use_loc, OPT_Wdangling_pointer_, + (maybe + ? G_("dangling pointer to a compound literal " + "may be used") + : G_("using a dangling pointer to a compound " + "literal")), + var))) + { + inform (DECL_SOURCE_LOCATION (var), + "compound literal defined here"); + suppress_warning (use_stmt, OPT_Wdangling_pointer_); + } } /* If STMT is a call to either the standard realloc or to a user-defined @@ -3647,10 +3806,14 @@ pointers_related_p (gimple *stmt, tree p, tree q, pointer_query &qry) /* For a STMT either a call to a deallocation function or a clobber, warn for uses of the pointer PTR it was called with (including its copies - or others derived from it by pointer arithmetic). */ + or others derived from it by pointer arithmetic). If STMT is a clobber, + VAR is the decl of the clobbered variable. When MAYBE is true use + a "maybe" form of diagnostic. */ void -pass_waccess::check_pointer_uses (gimple *stmt, tree ptr) +pass_waccess::check_pointer_uses (gimple *stmt, tree ptr, + tree var /* = NULL_TREE */, + bool maybe /* = false */) { gcc_assert (TREE_CODE (ptr) == SSA_NAME); @@ -3733,18 +3896,25 @@ pass_waccess::check_pointer_uses (gimple *stmt, tree ptr) /* Warn if USE_STMT is dominated by the deallocation STMT. Otherwise, add the pointer to POINTERS so that the uses of any other pointers derived from it can be checked. */ - if (use_after_inval_p (stmt, use_stmt)) + if (use_after_inval_p (stmt, use_stmt, check_dangling)) { - /* TODO: Handle PHIs but careful of false positives. */ - if (gimple_code (use_stmt) != GIMPLE_PHI) + if (gimple_code (use_stmt) == GIMPLE_PHI) { - basic_block use_bb = gimple_bb (use_stmt); - bool this_maybe - = !dominated_by_p (CDI_POST_DOMINATORS, use_bb, stmt_bb); - warn_invalid_pointer (*use_p->use, use_stmt, stmt, - this_maybe, equality); - continue; + tree lhs = gimple_phi_result (use_stmt); + if (TREE_CODE (lhs) == SSA_NAME) + { + pointers.safe_push (lhs); + continue; + } } + + basic_block use_bb = gimple_bb (use_stmt); + bool this_maybe + = (maybe + || !dominated_by_p (CDI_POST_DOMINATORS, use_bb, stmt_bb)); + warn_invalid_pointer (*use_p->use, use_stmt, stmt, var, + this_maybe, equality); + continue; } if (is_gimple_assign (use_stmt)) @@ -3779,26 +3949,99 @@ pass_waccess::check_call (gcall *stmt) if (gimple_call_builtin_p (stmt, BUILT_IN_NORMAL)) check_builtin (stmt); - if (tree callee = gimple_call_fndecl (stmt)) - { - /* Check for uses of the pointer passed to either a standard - or a user-defined deallocation function. */ - unsigned argno = fndecl_dealloc_argno (callee); - if (argno < (unsigned) call_nargs (stmt)) - { - tree arg = call_arg (stmt, argno); - if (TREE_CODE (arg) == SSA_NAME) - check_pointer_uses (stmt, arg); - } - } + if (!m_early_checks_p) + if (tree callee = gimple_call_fndecl (stmt)) + { + /* Check for uses of the pointer passed to either a standard + or a user-defined deallocation function. */ + unsigned argno = fndecl_dealloc_argno (callee); + if (argno < (unsigned) call_nargs (stmt)) + { + tree arg = call_arg (stmt, argno); + if (TREE_CODE (arg) == SSA_NAME) + check_pointer_uses (stmt, arg); + } + } check_call_access (stmt); + check_call_dangling (stmt); + + if (m_early_checks_p) + return; maybe_check_dealloc_call (stmt); check_nonstring_args (stmt); } +/* Return true of X is a DECL with automatic storage duration. */ + +static inline bool +is_auto_decl (tree x) +{ + return DECL_P (x) && !DECL_EXTERNAL (x) && !TREE_STATIC (x); +} + +/* Check non-call STMT for invalid accesses. */ + +void +pass_waccess::check_stmt (gimple *stmt) +{ + if (m_check_dangling_p && gimple_clobber_p (stmt)) + { + /* Ignore clobber statemts in blocks with exceptional edges. */ + basic_block bb = gimple_bb (stmt); + edge e = EDGE_PRED (bb, 0); + if (e->flags & EDGE_EH) + return; + + tree var = gimple_assign_lhs (stmt); + m_clobbers.put (var, stmt); + return; + } + + if (is_gimple_assign (stmt)) + { + /* Clobbered compound literals can be revived. Check for + an assignment to one and remove it from M_CLOBBERS. */ + tree lhs = gimple_assign_lhs (stmt); + while (handled_component_p (lhs)) + lhs = TREE_OPERAND (lhs, 0); + + if (is_auto_decl (lhs)) + m_clobbers.remove (lhs); + return; + } + + if (greturn *ret = dyn_cast (stmt)) + { + if (optimize && flag_isolate_erroneous_paths_dereference) + /* Avoid interfering with -Wreturn-local-addr (which runs only + with optimization enabled). */ + return; + + tree arg = gimple_return_retval (ret); + if (!arg || TREE_CODE (arg) != ADDR_EXPR) + return; + + arg = TREE_OPERAND (arg, 0); + while (handled_component_p (arg)) + arg = TREE_OPERAND (arg, 0); + + if (!is_auto_decl (arg)) + return; + + gimple **pclobber = m_clobbers.get (arg); + if (!pclobber) + return; + + if (!use_after_inval_p (*pclobber, stmt)) + return; + + warn_invalid_pointer (NULL_TREE, stmt, *pclobber, arg, false); + } +} + /* Check basic block BB for invalid accesses. */ void @@ -3811,6 +4054,8 @@ pass_waccess::check_block (basic_block bb) gimple *stmt = gsi_stmt (si); if (gcall *call = dyn_cast (stmt)) check_call (call); + else + check_stmt (stmt); } } @@ -3859,6 +4104,232 @@ pass_waccess::gimple_call_return_arg (gcall *call) return gimple_call_arg (call, argno); } +/* Return the decl referenced by the argument that the call STMT to + a built-in function returns (including with an offset) or null if + it doesn't. */ + +tree +pass_waccess::gimple_call_return_arg_ref (gcall *call) +{ + if (tree arg = gimple_call_return_arg (call)) + { + access_ref aref; + if (m_ptr_qry.get_ref (arg, call, &aref, 0) + && DECL_P (aref.ref)) + return aref.ref; + } + + return NULL_TREE; +} + +/* Check for and diagnose all uses of the dangling pointer VAR to + the auto object DECL whose lifetime has ended. */ + +void +pass_waccess::check_dangling_uses (tree var, tree decl, bool maybe /* = false */) +{ + if (!decl || !is_auto_decl (decl)) + return; + + gimple **pclob = m_clobbers.get (decl); + if (!pclob) + return; + + check_pointer_uses (*pclob, var, decl, maybe); +} + +/* Diagnose stores in BB and (recursively) its predecessors of the addresses + of local variables into nonlocal pointers that are left dangling after + the function returns. BBS is a bitmap of basic blocks visited. */ + +void +pass_waccess::check_dangling_stores (basic_block bb, + hash_set &stores, + auto_bitmap &bbs) +{ + if (!bitmap_set_bit (bbs, bb->index)) + /* Avoid cycles. */ + return; + + /* Iterate backwards over the statements looking for a store of + the address of a local variable into a nonlocal pointer. */ + for (auto gsi = gsi_last_nondebug_bb (bb); ; gsi_prev_nondebug (&gsi)) + { + gimple *stmt = gsi_stmt (gsi); + if (!stmt) + break; + + if (is_gimple_call (stmt) + && !(gimple_call_flags (stmt) & (ECF_CONST | ECF_PURE))) + /* Avoid looking before nonconst, nonpure calls since those might + use the escaped locals. */ + return; + + if (!is_gimple_assign (stmt) || gimple_clobber_p (stmt)) + continue; + + access_ref lhs_ref; + tree lhs = gimple_assign_lhs (stmt); + if (!m_ptr_qry.get_ref (lhs, stmt, &lhs_ref, 0)) + continue; + + if (is_auto_decl (lhs_ref.ref)) + continue; + + if (DECL_P (lhs_ref.ref)) + { + if (!POINTER_TYPE_P (TREE_TYPE (lhs_ref.ref)) + || lhs_ref.deref > 0) + continue; + } + else if (TREE_CODE (lhs_ref.ref) == SSA_NAME) + { + /* Avoid looking at or before stores into unknown objects. */ + gimple *def_stmt = SSA_NAME_DEF_STMT (lhs_ref.ref); + if (!gimple_nop_p (def_stmt)) + return; + } + else if (TREE_CODE (lhs_ref.ref) == MEM_REF) + { + tree arg = TREE_OPERAND (lhs_ref.ref, 0); + if (TREE_CODE (arg) == SSA_NAME) + { + gimple *def_stmt = SSA_NAME_DEF_STMT (arg); + if (!gimple_nop_p (def_stmt)) + return; + } + } + else + continue; + + if (stores.add (lhs_ref.ref)) + continue; + + access_ref rhs_ref; + tree rhs = gimple_assign_rhs1 (stmt); + if (!m_ptr_qry.get_ref (rhs, stmt, &rhs_ref, 0) + || rhs_ref.deref != -1) + continue; + + if (!is_auto_decl (rhs_ref.ref)) + continue; + + location_t loc = gimple_location (stmt); + if (warning_at (loc, OPT_Wdangling_pointer_, + "storing the address of local variable %qD in %qE", + rhs_ref.ref, lhs)) + { + location_t loc = DECL_SOURCE_LOCATION (rhs_ref.ref); + inform (loc, "%qD declared here", rhs_ref.ref); + + if (DECL_P (lhs_ref.ref)) + loc = DECL_SOURCE_LOCATION (lhs_ref.ref); + else if (EXPR_HAS_LOCATION (lhs_ref.ref)) + loc = EXPR_LOCATION (lhs_ref.ref); + + if (loc != UNKNOWN_LOCATION) + inform (loc, "%qE declared here", lhs_ref.ref); + } + } + + edge e; + edge_iterator ei; + FOR_EACH_EDGE (e, ei, bb->preds) + { + basic_block pred = e->src; + check_dangling_stores (pred, stores, bbs); + } +} + +/* Diagnose stores of the addresses of local variables into nonlocal + pointers that are left dangling after the function returns. */ + +void +pass_waccess::check_dangling_stores () +{ + auto_bitmap bbs; + hash_set stores; + check_dangling_stores (EXIT_BLOCK_PTR_FOR_FN (m_func), stores, bbs); +} + +/* Check for and diagnose uses of dangling pointers to auto objects + whose lifetime has ended. */ + +void +pass_waccess::check_dangling_uses () +{ + tree var; + unsigned i; + FOR_EACH_SSA_NAME (i, var, m_func) + { + if (TREE_CODE (TREE_TYPE (var)) != POINTER_TYPE) + continue; + + /* For each SSA_NAME pointer VAR find the DECL it points to. + If the DECL is a clobbered local variable, check to see + if any of VAR's uses (or those of other pointers derived + from VAR) happens after the clobber. If so, warn. */ + tree decl = NULL_TREE; + + gimple *def_stmt = SSA_NAME_DEF_STMT (var); + if (is_gimple_assign (def_stmt)) + { + tree rhs = gimple_assign_rhs1 (def_stmt); + if (TREE_CODE (rhs) == ADDR_EXPR) + decl = TREE_OPERAND (rhs, 0); + } + else if (gcall *call = dyn_cast(def_stmt)) + decl = gimple_call_return_arg_ref (call); + else if (gphi *phi = dyn_cast (def_stmt)) + { + unsigned nargs = gimple_phi_num_args (phi); + for (unsigned i = 0; i != nargs; ++i) + { + access_ref aref; + tree arg = gimple_phi_arg_def (phi, i); + if (!m_ptr_qry.get_ref (arg, phi, &aref, 0) + || (aref.deref == 0 + && POINTER_TYPE_P (TREE_TYPE (aref.ref)))) + continue; + check_dangling_uses (var, aref.ref, true); + } + continue; + } + else + continue; + + check_dangling_uses (var, decl); + } +} + +/* Check CALL arguments for dangling pointers (those that have been + clobbered) and warn if found. */ + +void +pass_waccess::check_call_dangling (gcall *call) +{ + unsigned nargs = gimple_call_num_args (call); + for (unsigned i = 0; i != nargs; ++i) + { + tree arg = gimple_call_arg (call, i); + if (TREE_CODE (arg) != ADDR_EXPR) + continue; + + arg = TREE_OPERAND (arg, 0); + if (!DECL_P (arg)) + continue; + + gimple **pclobber = m_clobbers.get (arg); + if (!pclobber) + continue; + + if (!use_after_inval_p (*pclobber, call)) + continue; + + warn_invalid_pointer (NULL_TREE, call, *pclobber, arg, false); + } +} + /* Check function FUN for invalid accesses. */ unsigned @@ -3871,6 +4342,15 @@ pass_waccess::execute (function *fun) m_ptr_qry.rvals = enable_ranger (fun); m_func = fun; + /* Check for dangling pointers in the earliest run of the pass. + The latest point -Wdanglinng-pointer should run is just before + loop unrolling which introduces uses after clobbers. Most cases + can be detected without optimization; cases where the address of + the local variable is passed to and then returned from a user- + defined function before its lifetime ends and the returned pointer + becomes dangling depend on inlining. */ + m_check_dangling_p = m_early_checks_p; + auto_bitmap bb_uids_set (&bitmap_default_obstack); m_bb_uids_set = bb_uids_set; @@ -3880,6 +4360,12 @@ pass_waccess::execute (function *fun) FOR_EACH_BB_FN (bb, fun) check_block (bb); + if (m_check_dangling_p) + { + check_dangling_uses (); + check_dangling_stores (); + } + if (dump_file) m_ptr_qry.dump (dump_file, (dump_flags & TDF_DETAILS) != 0); @@ -3890,6 +4376,7 @@ pass_waccess::execute (function *fun) disable_ranger (fun); m_ptr_qry.rvals = NULL; + m_clobbers.empty (); m_bb_uids_set = NULL; free_dominance_info (CDI_POST_DOMINATORS); diff --git a/gcc/passes.def b/gcc/passes.def index 37ea0d318d1..edf9c5be6a0 100644 --- a/gcc/passes.def +++ b/gcc/passes.def @@ -63,6 +63,7 @@ along with GCC; see the file COPYING3. If not see NEXT_PASS (pass_ubsan); NEXT_PASS (pass_nothrow); NEXT_PASS (pass_rebuild_cgraph_edges); + NEXT_PASS (pass_warn_access, /*early=*/true); POP_INSERT_PASSES () NEXT_PASS (pass_local_optimization_passes); @@ -201,6 +202,8 @@ along with GCC; see the file COPYING3. If not see form if possible. */ NEXT_PASS (pass_object_sizes); NEXT_PASS (pass_post_ipa_warn); + /* Must run before loop unrolling. */ + NEXT_PASS (pass_warn_access, /*early=*/true); NEXT_PASS (pass_complete_unrolli); NEXT_PASS (pass_backprop); NEXT_PASS (pass_phiprop); @@ -426,7 +429,7 @@ along with GCC; see the file COPYING3. If not see NEXT_PASS (pass_harden_compares); NEXT_PASS (pass_cleanup_cfg_post_optimizing); NEXT_PASS (pass_warn_function_noreturn); - NEXT_PASS (pass_warn_access); + NEXT_PASS (pass_warn_access, /*early=*/false); NEXT_PASS (pass_expand); diff --git a/gcc/testsuite/c-c++-common/Wdangling-pointer-2.c b/gcc/testsuite/c-c++-common/Wdangling-pointer-2.c new file mode 100644 index 00000000000..527e5e7b2c6 --- /dev/null +++ b/gcc/testsuite/c-c++-common/Wdangling-pointer-2.c @@ -0,0 +1,437 @@ +/* PR middle-end/63272 - GCC should warn when using pointer to dead scoped + variable within the same function + Exercise basic cases of -Wdangling-pointer with optimization. + { dg-do compile } + { dg-options "-O2 -Wall -Wno-uninitialized -Wno-return-local-addr -ftrack-macro-expansion=0" } */ + +typedef __INTPTR_TYPE__ intptr_t; +typedef __SIZE_TYPE__ size_t; + +#if __cplusplus +# define EXTERN_C extern "C" +#else +# define EXTERN_C extern +#endif + +#define NOIPA __attribute__ ((noipa)) + +EXTERN_C void* alloca (size_t); +EXTERN_C void* malloc (size_t); +EXTERN_C void* memchr (const void*, int, size_t); +EXTERN_C char* strchr (const char*, int); + +int sink (const void*, ...); +#define sink(...) sink (0, __VA_ARGS__) + + +NOIPA void nowarn_addr (void) +{ + int *p; + { + int a[] = { 1, 2, 3 }; + p = a; + } + + // This is suspect but not a clear error. + sink (&p); +} + + +NOIPA char* nowarn_ptr (void) +{ + char *p; + sink (&p); + return p; +} + + +NOIPA char* nowarn_cond_ptr (void) +{ + // Distilled from a false positive in Glibc dlerror.c. + char *q; + if (sink (&q)) + return q; + + return 0; +} + + +NOIPA void nowarn_loop_ptr (int n, int *p) +{ + // Distilled from a false positive in Glibc td_thr_get_info.c. + for (int i = 0; i != 2; ++i) + { + int x; + sink (&x); + *p++ = x; + } + + /* With the loop unrolled, Q is clobbered just before the call to + sink(), making it indistinguishable from passing it a pointer + to an out-of-scope variable. Verify that the warning doesn't + suffer from false positives due to this. + int * q; + int * q.1_17; + int * q.1_26; + + : + f (&q); + q.1_17 = q; + *p_5(D) = q.1_17; + q ={v} {CLOBBER}; + f (&q); + q.1_26 = q; + MEM[(void * *)p_5(D) + 8B] = q.1_26; + q ={v} {CLOBBER}; + return; + */ +} + + +NOIPA void nowarn_intptr_t (void) +{ + intptr_t ip; + { + int a[] = { 1, 2, 3 }; + ip = (intptr_t)a; + } + + // Using an intptr_t is not diagnosed. + sink (0, ip); +} + + +NOIPA void nowarn_string_literal (void) +{ + const char *s; + { + s = "123"; + } + + sink (s); +} + + +NOIPA void nowarn_extern_array (int x) +{ + { + /* This is a silly sanity check. */ + extern int eia[]; + int *p; + { + p = eia; + } + sink (p); + } +} + + +NOIPA void nowarn_static_array (int x) +{ + { + const char *s; + { + static const char sca[] = "123"; + s = sca; + } + + sink (s); + } + { + const int *p; + { + static const int sia[] = { 1, 2, 3 }; + p = sia; + } + + sink (p); + } + { + const int *p; + { + static const int sia[] = { 1, 2, 3 }; + p = (const int*)memchr (sia, x, sizeof sia); + } + + sink (p); + } +} + + +NOIPA void nowarn_alloca (unsigned n) +{ + { + char *p; + { + p = (char*)alloca (n); + } + sink (p); + } + { + int *p; + { + p = (int*)alloca (n * sizeof *p); + sink (p); + } + sink (p); + } + { + long *p; + { + p = (long*)alloca (n * sizeof *p); + sink (p); + p = p + 1; + } + sink (p); + } +} + + +#pragma GCC diagnostic push +/* Verify that -Wdangling-pointer works with #pragma diagnostic. */ +#pragma GCC diagnostic ignored "-Wdangling-pointer" + + +NOIPA void* nowarn_return_local_addr (void) +{ + int a[] = { 1, 2, 3 }; + int *p = a; + + /* This is a likely bug but it's not really one of using a dangling + pointer but rather of returning the address of a local variable + which is diagnosed by -Wreturn-local-addr. */ + return p; +} + +NOIPA void* warn_return_local_addr (void) +{ + int *p = 0; + { + int a[] = { 1, 2, 3 }; + sink (a); + p = a; + } + + /* Unlike the above case, here the pointer is dangling when it's + used. */ + return p; // { dg-warning "using dangling pointer 'p' to 'a'" "pr??????" { xfail *-*-* } } +} + + +NOIPA void* nowarn_return_alloca (int n) +{ + int *p = (int*)alloca (n); + sink (p); + + /* This is a likely bug but it's not really one of using a dangling + pointer but rather of returning the address of a local variable + which is diagnosed by -Wreturn-local-addr. */ + return p; +} + + +NOIPA void nowarn_scalar_call_ignored (void *vp) +{ + int *p; + { + int i; + p = &i; + } + sink (p); +} + +#pragma GCC diagnostic pop + +NOIPA void warn_scalar_call (void) +{ + int *p; + { + int i; // { dg-message "'i' declared" "note" } + p = &i; + } + // When the 'p' is optimized away it's not mentioned in the warning. + sink (p); // { dg-warning "using \(a \)?dangling pointer \('p' \)?to 'i'" "array" } +} + + +NOIPA void warn_array_call (void) +{ + int *p; + { + int a[] = { 1, 2, 3 }; // { dg-message "'a' declared" "note" } + p = a; + } + sink (p); // { dg-warning "using \(a \)?dangling pointer \('p' \)?to 'a'" "array" } +} + + +NOIPA void* warn_array_return (void) +{ + int *p; + { + int a[] = { 1, 2, 3 }; // { dg-message "'a' declared" "note" } + p = a; + } + + return p; // { dg-warning "using \(a \)?dangling pointer \('p' \)?to 'a'" "array" } +} + + +NOIPA void warn_pr63272_c1 (int i) +{ + int *p = 0; + + if (i) + { + int k = i; // { dg-message "'k' declared" "note" } + p = &k; + } + + sink (p ? *p : 0); // { dg-warning "dangling pointer 'p' to 'k' may be used" } +} + + +NOIPA void warn_pr63272_c4 (void) +{ + int *p = 0; + + { + int b; // { dg-message "'b' declared" "note" } + p = &b; + } + + sink (p); // { dg-warning "using \(a \)?dangling pointer \('p' \)?to 'b'" "scalar" } +} + + +NOIPA void warn_cond_if (int i, int n) +{ + int *p; + if (i) + { + int a[] = { 1, 2 }; // { dg-message "'a' declared" "note" } + sink (a); + p = a; + } + else + { + int *b = (int*)malloc (n); + sink (b); + p = b; + } + + sink (p); // { dg-warning "dangling pointer 'p' to 'a' may be used" } +} + + +NOIPA void warn_cond_else (int i, int n) +{ + int *p; + if (i) + { + int *a = (int*)malloc (n); + sink (a); + p = a; + } + else + { + int b[] = { 2, 3 }; + sink (b); + p = b; + } + + sink (p); // { dg-warning "dangling pointer 'p' to 'b' may be used" } +} + + +NOIPA void warn_cond_if_else (int i) +{ + int *p; + if (i) + { + int a[] = { 1, 2 }; // { dg-message "'a' declared" "note" } + sink (a); + p = a; + } + else + { + int b[] = { 3, 4 }; // { dg-message "'b' declared" "pr??????" { xfail *-*-* } } + sink (b); + p = b; + } + + /* With a PHI with more than invalid argument, only one use is diagnosed + because after the first diagnostic the code suppresses subsequent + ones for the same use. This needs to be fixed. */ + sink (p); // { dg-warning "dangling pointer 'p' to 'a' may be used" } + // { dg-warning "dangling pointer 'p' to 'b' may be used" "pr??????" { xfail *-*-* } .-1 } +} + + +NOIPA void nowarn_gcc_i386 (int i) +{ + // Regression test reduced from gcc's i386.c. + char a[32], *p; + + if (i != 1) + p = a; + else + p = 0; + + if (i == 2) + sink (p); + else + { + if (p) + { + sink (p); + return; + } + sink (p); + } +} + + +NOIPA void warn_memchr (char c1, char c2, char c3, char c4) +{ + char *p = 0; + { + char a[] = { c1, c2, c3 };// { dg-message "'a' declared" "note" } + p = (char*)memchr (a, c4, 3); + if (!p) + return; + } + + sink (p); // { dg-warning "using dangling pointer 'p' to 'a'" } +} + + +NOIPA void warn_strchr (char c1, char c2, char c3, char c4) +{ + char *p = 0; + { + char a[] = { c1, c2, c3 }; // { dg-message "'a' declared" "note" } + p = (char*)strchr (a, c4); + if (!p) + return; + } + + sink (p); // { dg-warning "using dangling pointer 'p' to 'a'" } +} + + +static inline int* return_arg (int *p) +{ + return p; +} + +NOIPA void warn_inline (int i1, int i2, int i3) +{ + int *p; + { + int a[] = { i1, i2, i3 }; // { dg-message "'a' declared" "note" } + p = return_arg (a); + } + + sink (p); // { dg-warning "using \(a \)?dangling pointer \('p' \)?to 'a'" "inline" } +} diff --git a/gcc/testsuite/c-c++-common/Wdangling-pointer-3.c b/gcc/testsuite/c-c++-common/Wdangling-pointer-3.c new file mode 100644 index 00000000000..d2f8f432eba --- /dev/null +++ b/gcc/testsuite/c-c++-common/Wdangling-pointer-3.c @@ -0,0 +1,64 @@ +/* PR middle-end/63272 - GCC should warn when using pointer to dead scoped + variable within the same function + Exercise conditional uses dangling pointers with optimization. + { dg-do compile } + { dg-options "-O2 -Wall -Wno-maybe-uninitialized" } */ + +typedef __INTPTR_TYPE__ intptr_t; +typedef __SIZE_TYPE__ size_t; + +#if __cplusplus +# define EXTERN_C extern "C" +#else +# define EXTERN_C extern +#endif + +EXTERN_C void* memcpy (void*, const void*, size_t); + +void sink (const void*, ...); + +char* nowarn_conditional (char *s) +{ + // Reduced from Glibc's tmpnam.c. + extern char a[5]; + char b[5]; + char *p = s ? s : b; + + sink (p); + + if (s == 0) + return a; + + return s; +} + + +char* nowarn_conditional_memcpy (char *s) +{ + // Reduced from Glibc's tmpnam.c. + extern char a[5]; + char b[5]; + char *p = s ? s : b; + + sink (p); + + if (s == 0) + return (char*)memcpy (a, p, 5); + + return s; +} + + +int warn_conditional_block (int i) +{ + int *p; + if (i) + { + int a[] = { 1, 2, 3 }; + p = &a[i]; + } + else + p = &i; + + return *p; // { dg-warning "dangling pointer \('p' \)to 'a' may be used" } +} diff --git a/gcc/testsuite/c-c++-common/Wdangling-pointer-4.c b/gcc/testsuite/c-c++-common/Wdangling-pointer-4.c new file mode 100644 index 00000000000..e57e66f8336 --- /dev/null +++ b/gcc/testsuite/c-c++-common/Wdangling-pointer-4.c @@ -0,0 +1,73 @@ +/* PR middle-end/63272 - GCC should warn when using pointer to dead scoped + variable within the same function + Exercise -Wdangling-pointer for VLAs. + { dg-do compile } + { dg-options "-O0 -Wall -ftrack-macro-expansion=0" } */ + +void sink (void*, ...); + +void nowarn_vla (int n) +{ + { + int vla1[n]; + int *p1 = vla1; + sink (p1); + + { + int vla2[n]; + int *p2 = vla2; + sink (p1, p2); + + { + int vla3[n]; + int *p3 = vla3; + sink (p1, p2, p3); + } + sink (p1, p2); + } + sink (p1); + } +} + +void warn_one_vla (int n) +{ + int *p; + { + int vla[n]; // { dg-message "'vla' declared" "pr??????" { xfail *-*-* } } + p = vla; + } + sink (p); // { dg-warning "using a dangling pointer to 'vla'" "vla" { xfail *-*-* } } +} + + +void warn_two_vlas_same_block (int n) +{ + int *p, *q; + { + int vla1[n]; // { dg-message "'vla1' declared" "pr??????" { xfail *-*-* } } + int vla2[n]; // { dg-message "'vla2' declared" "pr??????" { xfail *-*-* } } + p = vla1; + q = vla2; + } + + sink (p); // { dg-warning "using a dangling pointer to 'vla1'" "vla" { xfail *-*-* } } + sink (q); // { dg-warning "using a dangling pointer to 'vla2'" "vla" { xfail *-*-* } } +} + + +void warn_two_vlas_in_series (int n) +{ + int *p; + { + int vla1[n]; // { dg-message "'vla1' declared" "pr??????" { xfail *-*-* } } + p = vla1; + } + sink (p); // { dg-warning "using a dangling pointer to 'vla1'" "vla" { xfail *-*-* } } + + int *q; + { + int vla2[n]; // { dg-message "'vla2' declared" "pr??????" { xfail *-*-* } } + q = vla2; + } + sink (q); // { dg-warning "using a dangling pointer to 'vla2'" "vla" { xfail *-*-* } } +} diff --git a/gcc/testsuite/c-c++-common/Wdangling-pointer-5.c b/gcc/testsuite/c-c++-common/Wdangling-pointer-5.c new file mode 100644 index 00000000000..23d37960fad --- /dev/null +++ b/gcc/testsuite/c-c++-common/Wdangling-pointer-5.c @@ -0,0 +1,79 @@ +/* PR middle-end/63272 - GCC should warn when using pointer to dead scoped + variable within the same function + Exercise -Wdangling-pointer for VLAs. + { dg-do compile } + { dg-options "-O0 -Wall -ftrack-macro-expansion=0" } */ + +void* sink (void*, ...); + +extern void *evp; + +void nowarn_store_extern_call (void) +{ + int x; + evp = &x; + sink (0); +} + +void nowarn_store_extern_ovrwrite (void) +{ + int x; + evp = &x; + evp = 0; +} + +void nowarn_store_extern_store (void) +{ + int x; + void **p = (void**)sink (&evp); + evp = &x; + *p = 0; +} + + +void warn_store_extern (void) +{ + extern void *evp1; // { dg-message "'evp1' declared here" } + int x; // { dg-message "'x' declared here" } + evp1 = &x; // { dg-warning "storing the address of local variable 'x' in 'evp1'" } +} + + +void nowarn_store_arg_call (void **vpp) +{ + int x; + *vpp = &x; + sink (0); +} + +void nowarn_store_arg_ovrwrite (void **vpp) +{ + int x; + *vpp = &x; + *vpp = 0; +} + +void nowarn_store_arg_store (void **vpp) +{ + int x; + void **p = (void**)sink (0); + *vpp = &x; + *p = 0; +} + +void* nowarn_store_arg_store_arg (void **vpp1, void **vpp2) +{ + int x; + void **p = (void**)sink (0); + *vpp1 = &x; // warn here? + *vpp2 = 0; // might overwrite *vpp1 + return p; +} + +void warn_store_arg (void **vpp) +{ + int x; // { dg-message "'x' declared here" } + *vpp = &x; // { dg-warning "storing the address of local variable 'x' in '\\*vpp'" } +} + + diff --git a/gcc/testsuite/c-c++-common/Wdangling-pointer.c b/gcc/testsuite/c-c++-common/Wdangling-pointer.c new file mode 100644 index 00000000000..a65404f05c2 --- /dev/null +++ b/gcc/testsuite/c-c++-common/Wdangling-pointer.c @@ -0,0 +1,414 @@ +/* PR middle-end/63272 - GCC should warn when using pointer to dead scoped + variable within the same function + Exercise basic cases of -Wdangling-pointer without optimization. + { dg-do compile } + { dg-options "-O0 -Wall -Wno-uninitialized -ftrack-macro-expansion=0" } */ + +typedef __INTPTR_TYPE__ intptr_t; +typedef __SIZE_TYPE__ size_t; + +#if __cplusplus +# define EXTERN_C extern "C" +#else +# define EXTERN_C extern +#endif + +EXTERN_C void* alloca (size_t); +EXTERN_C void* malloc (size_t); +EXTERN_C void* memchr (const void*, int, size_t); +EXTERN_C char* strchr (const char*, int); + +int sink (const void*, ...); +#define sink(...) sink (0, __VA_ARGS__) + + +void nowarn_addr (void) +{ + int *p; + { + int a[] = { 1, 2, 3 }; + p = a; + } + + // This is suspect but not a clear error. + sink (&p); +} + + +char* nowarn_ptr (void) +{ + char *p; + sink (&p); + return p; +} + + +char* nowarn_cond_ptr (void) +{ + // Distilled from a false positive in Glibc dlerror.c. + char *q; + if (sink (&q)) + return q; + + return 0; +} + + +void nowarn_loop_ptr (int n, int *p) +{ + // Distilled from a false positive in Glibc td_thr_get_info.c. + for (int i = 0; i != 2; ++i) + { + int x; + sink (&x); + *p++ = x; + } +} + + +void nowarn_intptr_t (void) +{ + intptr_t ip; + { + int a[] = { 1, 2, 3 }; + ip = (intptr_t)a; + } + + // Using an intptr_t is not diagnosed. + sink (0, ip); +} + + +void nowarn_string_literal (void) +{ + const char *s; + { + s = "123"; + } + + sink (s); +} + + +void nowarn_extern_array (int x) +{ + { + /* This is a silly sanity check. */ + extern int eia[]; + int *p; + { + p = eia; + } + sink (p); + } +} + + +void nowarn_static_array (int x) +{ + { + const char *s; + { + static const char sca[] = "123"; + s = sca; + } + + sink (s); + } + { + const int *p; + { + static const int sia[] = { 1, 2, 3 }; + p = sia; + } + + sink (p); + } + { + const int *p; + { + static const int sia[] = { 1, 2, 3 }; + p = (const int*)memchr (sia, x, sizeof sia); + } + + sink (p); + } +} + + +void nowarn_alloca (unsigned n) +{ + { + char *p; + { + p = (char*)alloca (n); + } + sink (p); + } + { + int *p; + { + p = (int*)alloca (n * sizeof *p); + sink (p); + } + sink (p); + } + { + long *p; + { + p = (long*)alloca (n * sizeof *p); + sink (p); + p = p + 1; + } + sink (p); + } +} + + +#pragma GCC diagnostic push +/* Verify that -Wdangling-pointer works with #pragma diagnostic. */ +#pragma GCC diagnostic ignored "-Wdangling-pointer" + +void nowarn_scalar_call_ignored (void *vp) +{ + int *p; + { + int i; + p = &i; + } + sink (p); +} + +#pragma GCC diagnostic pop + + +void* nowarn_return_local_addr (void) +{ + int a[] = { 1, 2, 3 }; + int *p = a; + + /* This is a likely bug but it's not really one of using a dangling + pointer but rather of returning the address of a local variable + which is diagnosed by -Wreturn-local-addr. */ + return p; +} + +void* warn_return_local_addr (void) +{ + int *p = 0; + { + int a[] = { 1, 2, 3 }; + p = a; + } + + /* Unlike the above case, here the pointer is dangling when it's + used. */ + return p; // { dg-warning "using dangling pointer 'p' to 'a'" "array" } +} + + +void* nowarn_return_alloca (int n) +{ + int *p = (int*)alloca (n); + sink (p); + + /* This is a likely bug but it's not really one of using a dangling + pointer but rather of returning the address of a local variable + which is diagnosed by -Wreturn-local-addr. */ + return p; +} + + +void warn_scalar_call (void) +{ + int *p; + { + int i; // { dg-message "'i' declared" "note" } + p = &i; + } + sink (p); // { dg-warning "using dangling pointer 'p' to 'i'" "array" } +} + + +void warn_array_call (void) +{ + int *p; + { + int a[] = { 1, 2, 3 }; // { dg-message "'a' declared" "note" } + p = a; + } + sink (p); // { dg-warning "using dangling pointer 'p' to 'a'" "array" } +} + + +void* warn_array_return (void) +{ + int *p; + { + int a[] = { 1, 2, 3 }; // { dg-message "'a' declared" "note" } + p = a; + } + return p; // { dg-warning "using dangling pointer 'p' to 'a'" "array" } +} + + +void warn_pr63272_c1 (int i) +{ + int *p = 0; + + if (i) + { + int k = i; // { dg-message "'k' declared" "note" } + p = &k; + } + + sink (p ? *p : 0); // { dg-warning "dangling pointer 'p' to 'k' may be used" } +} + + +void warn_pr63272_c4 (void) +{ + int *p = 0; + + { + int b; // { dg-message "'b' declared" "note" } + p = &b; + } + + sink (p); // { dg-warning "using dangling pointer 'p' to 'b'" "scalar" } +} + +void nowarn_cond_if (int i, int n) +{ + int *p; + if (i) + { + int a[] = { 1, 2 }; + p = a; + sink (p); + } + else + { + int *b = (int*)malloc (n); + p = b; + sink (p); + } + + p = 0; +} + + +void warn_cond_if (int i, int n) +{ + int *p; + if (i) + { + int a[] = { 1, 2 }; // { dg-message "'a' declared" "note" } + sink (a); + p = a; + } + else + { + int *b = (int*)malloc (n); + sink (b); + p = b; + } + + sink (p); // { dg-warning "dangling pointer 'p' to 'a' may be used" } +} + + +void warn_cond_else (int i, int n) +{ + int *p; + if (i) + { + int *a = (int*)malloc (n); + sink (a); + p = a; + } + else + { + int b[] = { 2, 3 }; + sink (b); + p = b; + } + + sink (p); // { dg-warning "dangling pointer 'p' to 'b' may be used" } +} + + +void warn_cond_if_else (int i) +{ + int *p; + if (i) + { + int a[] = { 1, 2 }; // { dg-message "'a' declared" "note" } + sink (a); + p = a; + } + else + { + int b[] = { 3, 4 }; // { dg-message "'b' declared" "note" { xfail *-*-* } } + sink (b); + p = b; + } + + /* With a PHI with more than invalid argument, only one use is diagnosed + because after the first diagnostic the code suppresses subsequent + ones for the same use. This needs to be fixed. */ + sink (p); // { dg-warning "dangling pointer 'p' to 'a' may be used" } + // { dg-warning "dangling pointer 'p' to 'b' may be used" "pr??????" { xfail *-*-* } .-1 } +} + + +void nowarn_gcc_i386 (int i) +{ + // Regression test reduced from gcc's i386.c. + char a[32], *p; + + if (i != 1) + p = a; + else + p = 0; + + if (i == 2) + sink (p); + else + { + if (p) + { + sink (p); + return; + } + sink (p); + } +} + + +void warn_memchr (char c1, char c2, char c3, char c4) +{ + char *p = 0; + { + char a[] = { c1, c2, c3 };// { dg-message "'a' declared" "note" } + p = (char*)memchr (a, c4, 3); + if (!p) + return; + } + + sink (p); // { dg-warning "using dangling pointer 'p' to 'a'" } +} + + +void warn_strchr (char c1, char c2, char c3, char c4) +{ + char *p = 0; + { + char a[] = { c1, c2, c3 }; // { dg-message "'a' declared" "note" } + p = (char*)strchr (a, c4); + if (!p) + return; + } + + sink (p); // { dg-warning "using dangling pointer 'p' to 'a'" } +} diff --git a/gcc/testsuite/g++.dg/warn/Wdangling-pointer.C b/gcc/testsuite/g++.dg/warn/Wdangling-pointer.C new file mode 100644 index 00000000000..54950bfb0e2 --- /dev/null +++ b/gcc/testsuite/g++.dg/warn/Wdangling-pointer.C @@ -0,0 +1,34 @@ +/* { dg-do compile } + { dg-options "-Wall -Wno-class-memaccess" } */ + +extern "C" void* memset (void*, int, __SIZE_TYPE__); + +void sink (void*); + +struct S { S (); }; + +void nowarn_array_access () +{ + /* Verify that the clobber in the exceptional basic block doesn't + cause bogus warnings. */ + S a[1]; + memset (a, 0, sizeof a); + sink (a); +} + + +void nowarn_array_access_cond (int i) +{ + if (i) + { + S a1[1]; + memset (a1, 0, sizeof a1); + sink (a1); + } + else + { + S a2[2]; + memset (a2, 0, sizeof a2); + sink (a2); + } +} diff --git a/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object-6.C b/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object-6.C index 83b6ff9157c..91a87786ae0 100644 --- a/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object-6.C +++ b/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object-6.C @@ -1,5 +1,5 @@ /* { dg-do compile } - { dg-options "-O0 -Wall" } */ + { dg-options "-O0 -Wall -Wno-dangling-pointer -Wno-return-local-address" } */ #if __cplusplus < 201103L # define noexcept throw () @@ -18,6 +18,8 @@ extern void *p; void nowarn_placement_new () { char a[sizeof (A)]; + /* The store to the global p might trigger -Wdangling pointer or + -Wreturn-local-address (if/when it runs without optimization). */ p = new (a) A (); // { dg-bogus "-Wfree-nonheap-object" } } diff --git a/gcc/testsuite/gcc.dg/Wdangling-pointer-2.c b/gcc/testsuite/gcc.dg/Wdangling-pointer-2.c new file mode 100644 index 00000000000..b5882fef69d --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wdangling-pointer-2.c @@ -0,0 +1,82 @@ +/* Exercise conditional C-only uses of dangling pointers with optimization. + { dg-do compile } + { dg-options "-O2 -Wall" } */ + +typedef __SIZE_TYPE__ size_t; + +extern void* memchr (const void*, int, size_t); +extern char* strchr (const char*, int); + +void sink (void*, ...); + + +void nowarn_compound_literal (int i, int j) +{ + { + int *p = i ? (int[]){ 1, 2, 3 } : (int[]){ 4, 5, 6 }; + sink (p); + } + { + int a[] = { 1, 2, 3 }; + int *q = i ? (int[]){ 4, 5, 6 } : a; + int *p = &q[1]; + sink (p); + } + { + int *p = i ? (int[]){ 1, 2, 3 } : (int[]){ 4, 5, 6 }; + int *q = __builtin_memchr (p, 2, 3 * sizeof *p); + sink (q); + } + { + int a[] = { i, i + 1, i + 2, 3 }; + int *p = i ? (int[]){ j, j + 1, j + 2, 3 } : a; + int *q = __builtin_memchr (p, 3, 4 * sizeof *p); + sink (q); + } +} + + +void warn_maybe_compound_literal (int i, int j) +{ + int a[] = { 1, 2, 3 }, *p; + { + p = i ? (int[]){ 4, 5, 6 } : a; + } + // When the 'p' is optimized away it's not mentioned in the warning. + sink (p); // { dg-warning "dangling pointer \('p' \)?to a compound literal may be used" } +} + + +void warn_maybe_compound_literal_memchr (int i, int j, int x) +{ + int a[] = { 1, 2, 3 }, *p; + { + int *q = i ? (int[]){ 4, 5, 6 } : a; + p = memchr (q, x, 3 * sizeof *q); + } + sink (p); // { dg-warning "dangling pointer 'p' to a compound literal may be used" } +} + + +void warn_maybe_array (int i, int j) +{ + int a[] = { 1, 2, 3 }, *p; + { + int b[] = { 4, 5, 6 }; + p = i ? a : b; + } + // When the 'p' is optimized away it's not mentioned in the warning. + sink (p); // { dg-warning "dangling pointer \('p' \)?to 'b' may be used" } +} + + +void warn_maybe_array_memchr (int i, int j, int x) +{ + int a[] = { 1, 2, 3 }, *p; + { + int b[] = { 4, 5, 6 }; + int *q = i ? a : b; + p = memchr (q, x, 3 * sizeof *q); + } + sink (p); // { dg-warning "dangling pointer 'p' to 'b' may be used" } +} diff --git a/gcc/testsuite/gcc.dg/Wdangling-pointer.c b/gcc/testsuite/gcc.dg/Wdangling-pointer.c new file mode 100644 index 00000000000..e32ac7d7a46 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wdangling-pointer.c @@ -0,0 +1,62 @@ +/* Exercise basic C-only cases of -Wdangling-pointer. + { dg-do compile } + { dg-options "-O0 -Wall" } */ + +typedef __SIZE_TYPE__ size_t; + +extern void* memchr (const void*, int, size_t); +extern char* strchr (const char*, int); + +void sink (const void*, ...); + + +void nowarn_compound_literal (int i) +{ + { + int *p = (int[]){ 1, 2, 3 }; + sink (p); + } + { + int *q = (int[]){ 1, 2, 3 }; + int *p = &q[1]; + sink (p); + } + { + int *p = __builtin_memchr ((int[]){ 1, 2, 3 }, 2, 3 * sizeof *p); + sink (p); + } + { + int *p = __builtin_memchr ((int[]){ i, i + 1 }, 3, 2 * sizeof *p); + sink (p); + } +} + + +void warn_compound_literal (int i) +{ + int *p; + { + p = (int[]){ 1, 2, 3 }; // { dg-message "compound literal" }, + } + sink (p); // { dg-warning "using dangling pointer 'p' to a compound literal" } + + { + int *q = + (int[]){ 1, 2, 3 }; // { dg-message "compound literal" }, + p = &q[1]; + } + sink (p); // { dg-warning "using dangling pointer 'p' to a compound literal" } + { + p = (int*)memchr ( + (int[]){ 1, 2, 3 }, // { dg-message "compound literal" } + 2, 3 * sizeof *p); + } + sink (p); // { dg-warning "using dangling pointer 'p' to a compound literal" } + + { + p = (int*)memchr ( + (int[]){ i, i + 1 },// { dg-message "compound literal" } + 3, 2 * sizeof *p); + } + sink (p); // { dg-warning "using dangling pointer 'p' to a compound literal" } +}