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.
This commit is contained in:
Martin Sebor 2022-01-15 16:41:40 -07:00
parent 671a283636
commit 9d6a0f388e
18 changed files with 2043 additions and 61 deletions

View File

@ -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.

View File

@ -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;

View File

@ -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

View File

@ -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 *);
void warn_invalid_pointer (tree, gimple *, gimple *, bool, bool = false);
/* 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<tree> &, auto_bitmap &);
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<tree, gimple *> 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 "<unknown>" 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 <greturn *> (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 <gcall *> (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<tree> &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<tree> 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<gcall *>(def_stmt))
decl = gimple_call_return_arg_ref (call);
else if (gphi *phi = dyn_cast <gphi *>(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);

View File

@ -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);

View File

@ -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;
<bb 2>:
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" }
}

View File

@ -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" }
}

View File

@ -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 *-*-* } }
}

View File

@ -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'" }
}

View File

@ -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);
}

View File

@ -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'" }
}

View File

@ -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" }
}

View File

@ -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);
}
}

View File

@ -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" }
}

View File

@ -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" } */

View File

@ -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" }
}

View File

@ -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 *-*-* } }
}

View File

@ -7,7 +7,7 @@ int *x = 0;
void f (void)
{
int y = 1;
x = &y;
x = &y; // { dg-warning "\\\[-Wdangling-pointer" }
}
int g (void)