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:
parent
671a283636
commit
9d6a0f388e
18 changed files with 2043 additions and 61 deletions
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
437
gcc/testsuite/c-c++-common/Wdangling-pointer-2.c
Normal file
437
gcc/testsuite/c-c++-common/Wdangling-pointer-2.c
Normal 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" }
|
||||
}
|
64
gcc/testsuite/c-c++-common/Wdangling-pointer-3.c
Normal file
64
gcc/testsuite/c-c++-common/Wdangling-pointer-3.c
Normal 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" }
|
||||
}
|
73
gcc/testsuite/c-c++-common/Wdangling-pointer-4.c
Normal file
73
gcc/testsuite/c-c++-common/Wdangling-pointer-4.c
Normal 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 *-*-* } }
|
||||
}
|
90
gcc/testsuite/c-c++-common/Wdangling-pointer-5.c
Normal file
90
gcc/testsuite/c-c++-common/Wdangling-pointer-5.c
Normal 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'" }
|
||||
}
|
||||
|
||||
|
32
gcc/testsuite/c-c++-common/Wdangling-pointer-6.c
Normal file
32
gcc/testsuite/c-c++-common/Wdangling-pointer-6.c
Normal 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);
|
||||
}
|
434
gcc/testsuite/c-c++-common/Wdangling-pointer.c
Normal file
434
gcc/testsuite/c-c++-common/Wdangling-pointer.c
Normal 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'" }
|
||||
}
|
23
gcc/testsuite/g++.dg/warn/Wdangling-pointer-2.C
Normal file
23
gcc/testsuite/g++.dg/warn/Wdangling-pointer-2.C
Normal 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" }
|
||||
}
|
74
gcc/testsuite/g++.dg/warn/Wdangling-pointer.C
Normal file
74
gcc/testsuite/g++.dg/warn/Wdangling-pointer.C
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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" }
|
||||
}
|
||||
|
||||
|
|
|
@ -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" } */
|
||||
|
|
82
gcc/testsuite/gcc.dg/Wdangling-pointer-2.c
Normal file
82
gcc/testsuite/gcc.dg/Wdangling-pointer-2.c
Normal 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" }
|
||||
}
|
75
gcc/testsuite/gcc.dg/Wdangling-pointer.c
Normal file
75
gcc/testsuite/gcc.dg/Wdangling-pointer.c
Normal 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 *-*-* } }
|
||||
}
|
|
@ -7,7 +7,7 @@ int *x = 0;
|
|||
void f (void)
|
||||
{
|
||||
int y = 1;
|
||||
x = &y;
|
||||
x = &y; // { dg-warning "\\\[-Wdangling-pointer" }
|
||||
}
|
||||
|
||||
int g (void)
|
||||
|
|
Loading…
Add table
Reference in a new issue