From 9d6a0f388eb048f8d87f47af78f07b5ce513bfe6 Mon Sep 17 00:00:00 2001 From: Martin Sebor Date: Sat, 15 Jan 2022 16:41:40 -0700 Subject: [PATCH] Add -Wdangling-pointer [PR63272]. Resolves: PR c/63272 - GCC should warn when using pointer to dead scoped variable with in 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-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. * g++.dg/warn/ref-temp1.C: Prune expected warning. * 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-6.c: New test. * c-c++-common/Wdangling-pointer.c: New test. * g++.dg/warn/Wdangling-pointer-2.C: New test. * g++.dg/warn/Wdangling-pointer.C: New test. * gcc.dg/Wdangling-pointer-2.c: New test. * gcc.dg/Wdangling-pointer.c: New test. --- gcc/c-family/c.opt | 8 + gcc/diagnostic-spec.c | 1 + gcc/doc/invoke.texi | 62 +- gcc/gimple-ssa-warn-access.cc | 635 +++++++++++++++++++-- gcc/passes.def | 5 +- gcc/testsuite/c-c++-common/Wdangling-pointer-2.c | 437 ++++++++++++++ gcc/testsuite/c-c++-common/Wdangling-pointer-3.c | 64 +++ gcc/testsuite/c-c++-common/Wdangling-pointer-4.c | 73 +++ gcc/testsuite/c-c++-common/Wdangling-pointer-5.c | 90 +++ gcc/testsuite/c-c++-common/Wdangling-pointer-6.c | 32 ++ gcc/testsuite/c-c++-common/Wdangling-pointer.c | 434 ++++++++++++++ gcc/testsuite/g++.dg/warn/Wdangling-pointer-2.C | 23 + gcc/testsuite/g++.dg/warn/Wdangling-pointer.C | 74 +++ gcc/testsuite/g++.dg/warn/Wfree-nonheap-object-6.C | 4 +- gcc/testsuite/g++.dg/warn/ref-temp1.C | 3 + gcc/testsuite/gcc.dg/Wdangling-pointer-2.c | 82 +++ gcc/testsuite/gcc.dg/Wdangling-pointer.c | 75 +++ gcc/testsuite/gcc.dg/uninit-pr50476.c | 2 +- 18 files changed, 2043 insertions(+), 61 deletions(-) create mode 100644 gcc/testsuite/c-c++-common/Wdangling-pointer-2.c create mode 100644 gcc/testsuite/c-c++-common/Wdangling-pointer-3.c create mode 100644 gcc/testsuite/c-c++-common/Wdangling-pointer-4.c create mode 100644 gcc/testsuite/c-c++-common/Wdangling-pointer-5.c create mode 100644 gcc/testsuite/c-c++-common/Wdangling-pointer-6.c create mode 100644 gcc/testsuite/c-c++-common/Wdangling-pointer.c create mode 100644 gcc/testsuite/g++.dg/warn/Wdangling-pointer-2.C create mode 100644 gcc/testsuite/g++.dg/warn/Wdangling-pointer.C create mode 100644 gcc/testsuite/gcc.dg/Wdangling-pointer-2.c create mode 100644 gcc/testsuite/gcc.dg/Wdangling-pointer.c diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt index 2836364..db65c14 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 c9e1c1b..a8af229 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 121c8ea..7f2205e 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 @@ -4389,6 +4390,8 @@ Warn about overriding virtual functions that are not marked with the @opindex Wno-use-after-free Warn about uses of pointers to dynamically allocated objects that have been rendered indeterminate by a call to a deallocation function. +The warning is enabled at all optimization levels but may yield different +results with optimization than without. @table @gcctabopt @item -Wuse-after-free=1 @@ -5714,6 +5717,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 @@ -8587,6 +8591,62 @@ 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 (or C++ references) to objects with automatic +storage duration after their lifetime has ended. This includes local +variables declared in nested blocks, compound literals and other unnamed +temporary objects. In addition, warn about storing the address of such +objects in escaped pointers. The warning is enabled at all optimization +levels but may yield different results with optimization than without. + +@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 +In the following function the store of the address of the local variable +@code{x} in the escaped pointer @code{*p} also triggers the warning. +@smallexample +void g (int **p) +@{ + int x = 7; + *p = &x; // warning: storing the address of a local variable in *p +@} +@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 8821291..f639807 100644 --- a/gcc/gimple-ssa-warn-access.cc +++ b/gcc/gimple-ssa-warn-access.cc @@ -2069,10 +2069,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: @@ -2089,6 +2091,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); @@ -2112,26 +2117,41 @@ private: void check_atomic_memmodel (gimple *, tree, tree, const unsigned char *); /* 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, 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. */ @@ -2140,11 +2160,22 @@ 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. */ pass_waccess::~pass_waccess () @@ -2152,6 +2183,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 @@ -2340,6 +2379,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 @@ -2361,6 +2403,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)) { @@ -2413,6 +2462,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; @@ -2438,6 +2490,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; @@ -2507,6 +2562,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); @@ -2545,7 +2603,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); @@ -2569,7 +2627,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); @@ -2674,6 +2732,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 @@ -2695,7 +2756,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))) @@ -2938,7 +2999,7 @@ pass_waccess::check_atomic_memmodel (gimple *stmt, tree ord_sucs, if (warning_suppressed_p (stmt, OPT_Winvalid_memory_model)) return; - if (maybe_warn_memmodel (stmt, ord_sucs, ord_fail, valid)) + if (!maybe_warn_memmodel (stmt, ord_sucs, ord_fail, valid)) return; suppress_warning (stmt, OPT_Winvalid_memory_model); @@ -3094,11 +3155,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: @@ -3725,16 +3787,67 @@ 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 false; + 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 @@ -3752,27 +3865,30 @@ pass_waccess::use_after_inval_p (gimple *inval_stmt, gimple *use_stmt) return gimple_uid (inval_stmt) < gimple_uid (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. */ +/* Issue a warning for the USE_STMT of pointer or reference REF rendered + invalid by INVAL_STMT. REF may be null when it's been optimized away. + When nonnull, INVAL_STMT is the deallocation function that rendered + the pointer or reference dangling. Otherwise, VAR is the auto variable + (including an unnamed temporary such as a 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, - bool maybe, - bool equality /* = false */) +pass_waccess::warn_invalid_pointer (tree ref, gimple *use_stmt, + gimple *inval_stmt, tree var, + bool maybe, bool equality /* = false */) { /* Avoid printing the unhelpful "" in the diagnostics. */ - if (ptr && TREE_CODE (ptr) == SSA_NAME - && (!SSA_NAME_VAR (ptr) || DECL_ARTIFICIAL (SSA_NAME_VAR (ptr)))) - ptr = NULL_TREE; + if (ref && TREE_CODE (ref) == SSA_NAME + && (!SSA_NAME_VAR (ref) || DECL_ARTIFICIAL (SSA_NAME_VAR (ref)))) + ref = NULL_TREE; location_t use_loc = gimple_location (use_stmt); if (use_loc == UNKNOWN_LOCATION) { - use_loc = cfun->function_end_locus; - if (!ptr) + use_loc = m_func->function_end_locus; + if (!ref) /* Avoid issuing a warning with no context other than the function. That would make it difficult to debug in any but very simple cases. */ @@ -3788,12 +3904,12 @@ pass_waccess::warn_invalid_pointer (tree ptr, gimple *use_stmt, const tree inval_decl = gimple_call_fndecl (inval_stmt); - if ((ptr && warning_at (use_loc, OPT_Wuse_after_free, + if ((ref && warning_at (use_loc, OPT_Wuse_after_free, (maybe ? G_("pointer %qE may be used after %qD") : G_("pointer %qE used after %qD")), - ptr, inval_decl)) - || (!ptr && warning_at (use_loc, OPT_Wuse_after_free, + ref, inval_decl)) + || (!ref && warning_at (use_loc, OPT_Wuse_after_free, (maybe ? G_("pointer may be used after %qD") : G_("pointer used after %qD")), @@ -3805,6 +3921,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 ((ref + && warning_at (use_loc, OPT_Wdangling_pointer_, + (maybe + ? G_("dangling pointer %qE to %qD may be used") + : G_("using dangling pointer %qE to %qD")), + ref, var)) + || (!ref + && 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 ((ref + && warning_at (use_loc, OPT_Wdangling_pointer_, + (maybe + ? G_("dangling pointer %qE to an unnamed temporary " + "may be used") + : G_("using dangling pointer %qE to an unnamed " + "temporary")), + ref, var)) + || (!ref + && warning_at (use_loc, OPT_Wdangling_pointer_, + (maybe + ? G_("dangling pointer to an unnamed temporary " + "may be used") + : G_("using a dangling pointer to an unnamed " + "temporary")), + var))) + { + inform (DECL_SOURCE_LOCATION (var), + "unnamed temporary defined here"); + suppress_warning (use_stmt, OPT_Wdangling_pointer_); + } } /* If STMT is a call to either the standard realloc or to a user-defined @@ -3927,10 +4089,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); @@ -4013,18 +4179,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)) @@ -4059,26 +4232,100 @@ 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 unnamed temporaries such as 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 @@ -4091,6 +4338,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); } } @@ -4139,6 +4388,262 @@ 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. OBJREF is true when VAR denotes + an access to a DECL that may have been clobbered. */ + +void +pass_waccess::check_dangling_uses (tree var, tree decl, bool maybe /* = false */, + bool objref /* = false */) +{ + if (!decl || !is_auto_decl (decl)) + return; + + gimple **pclob = m_clobbers.get (decl); + if (!pclob) + return; + + if (!objref) + { + check_pointer_uses (*pclob, var, decl, maybe); + return; + } + + gimple *use_stmt = SSA_NAME_DEF_STMT (var); + if (!use_after_inval_p (*pclob, use_stmt, true)) + return; + + basic_block use_bb = gimple_bb (use_stmt); + basic_block clob_bb = gimple_bb (*pclob); + maybe = maybe || !dominated_by_p (CDI_POST_DOMINATORS, use_bb, clob_bb); + warn_invalid_pointer (var, use_stmt, *pclob, decl, maybe, false); +} + +/* 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; + + /* FIXME: Handle stores of alloca() and VLA. */ + 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) + { + /* 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) + { + if (!POINTER_TYPE_P (TREE_TYPE (var))) + continue; + decl = TREE_OPERAND (rhs, 0); + } + else + { + /* For other expressions, check the base DECL to see + if it's been clobbered, most likely as a result of + inlining a reference to it. */ + decl = get_base_address (rhs); + if (DECL_P (decl)) + check_dangling_uses (var, decl, false, true); + continue; + } + } + else if (POINTER_TYPE_P (TREE_TYPE (var))) + { + 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 @@ -4151,6 +4656,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 -Wdangling-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; @@ -4160,6 +4674,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); @@ -4170,6 +4690,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 1f77ab5..7880842 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 0000000..527e5e7 --- /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 0000000..d2f8f43 --- /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 0000000..e57e66f --- /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 0000000..1f549ca --- /dev/null +++ b/gcc/testsuite/c-c++-common/Wdangling-pointer-5.c @@ -0,0 +1,90 @@ +/* PR middle-end/63272 - GCC should warn when using pointer to dead scoped + variable within the same function + Exercise -Wdangling-pointer for escaping stores of addreses of auto + variables. + { dg-do compile } + { dg-options "-O0 -Wall -ftrack-macro-expansion=0" } */ + +void* alloca (__SIZE_TYPE__); + +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_alloca (int n) +{ + // This fails because of a bug in the warning. + void *p = alloca (n); + evp = p; // { dg-warning "storing the address of local variable 'x' in 'evp1'" "pr??????" { xfail *-*-* } } +} + + +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-6.c b/gcc/testsuite/c-c++-common/Wdangling-pointer-6.c new file mode 100644 index 0000000..9c05891 --- /dev/null +++ b/gcc/testsuite/c-c++-common/Wdangling-pointer-6.c @@ -0,0 +1,32 @@ +/* PR middle-end/63272 - GCC should warn when using pointer to dead scoped + variable within the same function + Exercise -Wdangling-pointer with inlining. + { dg-do compile } + { dg-options "-O1 -Wall" } */ + +void* sink (void*, ...); + +extern int *eip; // { dg-message "'eip' declared here" } + +static inline void store (int **p, int *q) +{ + *p = q; // { dg-warning "storing the address of local variable 'auto_x' in 'eip'" } +} + +void nowarn_inlined_store_extern (void) +{ + extern int extern_x; + store (&eip, &extern_x); +} + +void nowarn_inlined_store_static (void) +{ + static int static_x; + store (&eip, &static_x); +} + +void warn_inlined_store_auto (void) +{ + int auto_x; // { dg-message "'auto_x' declared here" } + store (&eip, &auto_x); +} 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 0000000..394ff92 --- /dev/null +++ b/gcc/testsuite/c-c++-common/Wdangling-pointer.c @@ -0,0 +1,434 @@ +/* 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__) + +/* Verify that integer assignments don't cause bogus warnings. + Reduced from GFlibc's s_nextafter.c. */ + +int nowarn_integer (float x) +{ + int i; + + { + union + { + float x; + int i; + } u; + + u.x = x; + i = u.i; + } + + return i; +} + +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-2.C b/gcc/testsuite/g++.dg/warn/Wdangling-pointer-2.C new file mode 100644 index 0000000..151418f --- /dev/null +++ b/gcc/testsuite/g++.dg/warn/Wdangling-pointer-2.C @@ -0,0 +1,23 @@ +/* { dg-do compile } + { dg-options "-O1 -Wall -Wno-class-memaccess" } */ + +struct A { A (); }; + +const A& return_arg (const A &a) +{ + return a; +} + +void sink (const void*); + +void nowarn_ref (int i) +{ + const A &a = return_arg (A ()); // { dg-note "unnamed temporary" } + sink (&a); // { dg-warning "-Wdangling-pointer" } +} + +void warn_dangling_ref (int i) +{ + const A &a = return_arg (A ()); // { dg-note "unnamed temporary" } + sink (&a); // { dg-warning "-Wdangling-pointer" } +} 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 0000000..22c559e --- /dev/null +++ b/gcc/testsuite/g++.dg/warn/Wdangling-pointer.C @@ -0,0 +1,74 @@ +/* Exercise basic C++-only cases of -Wdangling-pointer. + { dg-do compile } + { dg-options "-Wall -Wno-class-memaccess" } */ + +extern "C" void* memset (void*, int, __SIZE_TYPE__); + +void sink (const void*, ...); + +struct S { S (); }; + +void nowarn_int_ref (int i) +{ + const S &sref = S (); + const int &iref = 1 + i; + sink (&sref, &iref); +} + +void warn_init_ref_member () +{ + struct AS + { + const S &sref; + AS (): + // The temporary S object is destroyed when AS::AS() returns. + sref (S ()) // { dg-warning "storing the address" } + { } + } as; + + struct Ai + { + const int &iref; + Ai (): + // The temporary int is destroyed when Ai::Ai() returns. + iref (1 + 1) // { dg-warning "storing the address" } + { } + } ai; + + sink (&as, &ai); +} + + +void default_ref_arg (const S& = S ()); + +void nowarn_call_default_ref_arg () +{ + default_ref_arg (); +} + + +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 83b6ff9..91a8778 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/g++.dg/warn/ref-temp1.C b/gcc/testsuite/g++.dg/warn/ref-temp1.C index 26f1ca5..b09d921 100644 --- a/gcc/testsuite/g++.dg/warn/ref-temp1.C +++ b/gcc/testsuite/g++.dg/warn/ref-temp1.C @@ -9,3 +9,6 @@ struct Y { }; Y::Y () : x(1) {} // { dg-warning "temporary" } + +/* The initialization of x with the temporary might also trigger: + { dg-prune-output "-Wdangling-pointer" } */ 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 0000000..0170263 --- /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 an unnamed temporary 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 an unnamed temporary 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 0000000..d792d09 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wdangling-pointer.c @@ -0,0 +1,75 @@ +/* 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 "unnamed temporary" }, + } + sink (p); // { dg-warning "using dangling pointer 'p' to an unnamed temporary" } + + { + int *q = + (int[]){ 1, 2, 3 }; // { dg-message "unnamed temporary" }, + p = &q[1]; + } + sink (p); // { dg-warning "using dangling pointer 'p' to an unnamed temporary" } + { + p = (int*)memchr ( + (int[]){ 1, 2, 3 }, // { dg-message "unnamed temporary" } + 2, 3 * sizeof *p); + } + sink (p); // { dg-warning "using dangling pointer 'p' to an unnamed temporary" } + + { + p = (int*)memchr ( + (int[]){ i, i + 1 },// { dg-message "unnamed temporary" } + 3, 2 * sizeof *p); + } + sink (p); // { dg-warning "using dangling pointer 'p' to an unnamed temporary" } +} + + +void warn_store_compound_literal (int **p) +{ + int *q = (int[]) { 1, 2, 3 }; + p[0] = q; // { dg-warning "storing the address" } +} + +void warn_store_vla (int n, int **p) +{ + int a[n]; + p[1] = &a[1]; // { dg-warning "-Wdangling-pointer" "pr??????" { xfail *-*-* } } +} diff --git a/gcc/testsuite/gcc.dg/uninit-pr50476.c b/gcc/testsuite/gcc.dg/uninit-pr50476.c index db4146d..3720184 100644 --- a/gcc/testsuite/gcc.dg/uninit-pr50476.c +++ b/gcc/testsuite/gcc.dg/uninit-pr50476.c @@ -7,7 +7,7 @@ int *x = 0; void f (void) { int y = 1; - x = &y; + x = &y; // { dg-warning "\\\[-Wdangling-pointer" } } int g (void) -- 2.7.4