Add -Wuse-after-free [PR80532].

gcc/c-family/ChangeLog

	PR tree-optimization/80532
	* c.opt (-Wuse-after-free): New options.

gcc/ChangeLog:

	PR tree-optimization/80532
	* common.opt (-Wuse-after-free): New options.
	* diagnostic-spec.c (nowarn_spec_t::nowarn_spec_t): Handle
	OPT_Wreturn_local_addr and OPT_Wuse_after_free_.
	* diagnostic-spec.h (NW_DANGLING): New enumerator.
	* doc/invoke.texi (-Wuse-after-free): Document new option.
	* gimple-ssa-warn-access.cc (pass_waccess::check_call): Rename...
	(pass_waccess::check_call_access): ...to this.
	(pass_waccess::check): Rename...
	(pass_waccess::check_block): ...to this.
	(pass_waccess::check_pointer_uses): New function.
	(pass_waccess::gimple_call_return_arg): New function.
	(pass_waccess::warn_invalid_pointer): New function.
	(pass_waccess::check_builtin): Handle free and realloc.
	(gimple_use_after_inval_p): New function.
	(get_realloc_lhs): New function.
	(maybe_warn_mismatched_realloc): New function.
	(pointers_related_p): New function.
	(pass_waccess::check_call): Call check_pointer_uses.
	(pass_waccess::execute): Compute and free dominance info.

libcpp/ChangeLog:

	* files.c (_cpp_find_file): Substitute a valid pointer for
	an invalid one to avoid -Wuse-after-free.

libiberty/ChangeLog:

	* regex.c: Suppress -Wuse-after-free.

gcc/testsuite/ChangeLog:

	PR tree-optimization/80532
	* gcc.dg/Wmismatched-dealloc-2.c: Avoid -Wuse-after-free.
	* gcc.dg/Wmismatched-dealloc-3.c: Same.
	* gcc.dg/analyzer/file-1.c: Prune expected warning.
	* gcc.dg/analyzer/file-2.c: Same.
	* gcc.dg/attr-alloc_size-6.c: Disable -Wuse-after-free.
	* gcc.dg/attr-alloc_size-7.c: Same.
	* c-c++-common/Wuse-after-free-2.c: New test.
	* c-c++-common/Wuse-after-free-3.c: New test.
	* c-c++-common/Wuse-after-free-4.c: New test.
	* c-c++-common/Wuse-after-free-5.c: New test.
	* c-c++-common/Wuse-after-free-6.c: New test.
	* c-c++-common/Wuse-after-free-7.c: New test.
	* c-c++-common/Wuse-after-free.c: New test.
	* g++.dg/warn/Wmismatched-dealloc-3.C: New test.
	* g++.dg/warn/Wuse-after-free.C: New test.
This commit is contained in:
Martin Sebor 2022-01-15 16:37:54 -07:00
parent 29401b7b45
commit 671a283636
23 changed files with 1625 additions and 32 deletions

View File

@ -1366,6 +1366,18 @@ Wunused-const-variable=
C ObjC C++ ObjC++ Joined RejectNegative UInteger Var(warn_unused_const_variable) Warning LangEnabledBy(C ObjC,Wunused-variable, 1, 0) IntegerRange(0, 2)
Warn when a const variable is unused.
# Defining these options here in addition to common.opt is necessary
# in order for the default -Wall setting of -Wuse-after-free=2 to take
# effect.
Wuse-after-free
LangEnabledBy(C ObjC C++ LTO ObjC++)
; in common.opt
Wuse-after-free=
LangEnabledBy(C ObjC C++ LTO ObjC++, Wall,2,0)
; in common.opt
Wvariadic-macros
C ObjC C++ ObjC++ CPP(warn_variadic_macros) CppReason(CPP_W_VARIADIC_MACROS) Var(cpp_warn_variadic_macros) Init(0) Warning LangEnabledBy(C ObjC C++ ObjC++,Wpedantic || Wtraditional)
Warn about using variadic macros.

View File

@ -552,6 +552,14 @@ Warray-bounds=
Common Joined RejectNegative UInteger Var(warn_array_bounds) Warning IntegerRange(0, 2)
Warn if an array is accessed out of bounds.
Wuse-after-free
Common Var(warn_use_after_free) Warning
Warn for uses of pointers to deallocated strorage.
Wuse-after-free=
Common Joined RejectNegative UInteger Var(warn_use_after_free) Warning IntegerRange(0, 3)
Warn for uses of pointers to deallocated strorage.
Wattributes
Common Var(warn_attributes) Init(1) Warning
Warn about inappropriate attribute usage.

View File

@ -99,6 +99,11 @@ nowarn_spec_t::nowarn_spec_t (opt_code opt)
m_bits = NW_UNINIT;
break;
case OPT_Wreturn_local_addr:
case OPT_Wuse_after_free_:
m_bits = NW_DANGLING;
break;
default:
/* A catchall group for everything else. */
m_bits = NW_OTHER;

View File

@ -41,11 +41,13 @@ public:
NW_UNINIT = 1 << 3,
/* Warnings about arithmetic overflow. */
NW_VFLOW = 1 << 4,
/* Warnings about dangling pointers. */
NW_DANGLING = 1 << 5,
/* All other unclassified warnings. */
NW_OTHER = 1 << 5,
NW_OTHER = 1 << 6,
/* All groups of warnings. */
NW_ALL = (NW_ACCESS | NW_LEXICAL | NW_NONNULL
| NW_UNINIT | NW_VFLOW | NW_OTHER)
| NW_UNINIT | NW_VFLOW | NW_DANGLING | NW_OTHER)
};
nowarn_spec_t (): m_bits () { }

View File

@ -4383,6 +4383,65 @@ annotations.
Warn about overriding virtual functions that are not marked with the
@code{override} keyword.
@item -Wuse-after-free
@itemx -Wuse-after-free=@var{n}
@opindex Wuse-after-free
@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.
@table @gcctabopt
@item -Wuse-after-free=1
At level 1 the warning attempts to diagnose only unconditional uses
of pointers made indeterminate by a deallocation call or a successful
call to @code{realloc}, regardless of whether or not the call resulted
in an actual reallocatio of memory. This includes double-@code{free}
calls as well as uses in arithmetic and relational expressions. Although
undefined, uses of indeterminate pointers in equality (or inequality)
expressions are not diagnosed at this level.
@item -Wuse-after-free=2
At level 2, in addition to unconditional uses, the warning also diagnoses
conditional uses of pointers made indeterminate by a deallocation call.
As at level 2, uses in equality (or inequality) expressions are not
diagnosed. For example, the second call to @code{free} in the following
function is diagnosed at this level:
@smallexample
struct A @{ int refcount; void *data; @};
void release (struct A *p)
@{
int refcount = --p->refcount;
free (p);
if (refcount == 0)
free (p->data); // warning: p may be used after free
@}
@end smallexample
@item -Wuse-after-free=3
At level 3, the warning also diagnoses uses of indeterminate pointers in
equality expressions. All uses of indeterminate pointers are undefined
but equality tests sometimes appear after calls to @code{realloc} as
an attempt to determine whether the call resulted in relocating the object
to a different address. They are diagnosed at a separate level to aid
legacy code gradually transition to safe alternatives. For example,
the equality test in the function below is diagnosed at this level:
@smallexample
void adjust_pointers (int**, int);
void grow (int **p, int n)
@{
int **q = (int**)realloc (p, n *= 2);
if (q == p)
return;
adjust_pointers ((int**)q, n);
@}
@end smallexample
To avoid the warning at this level, store offsets into allocated memory
instead of pointers. This approach obviates needing to adjust the stored
pointers after reallocation.
@end table
@option{-Wuse-after-free=2} is included in @option{-Wall}.
@item -Wuseless-cast @r{(C++ and Objective-C++ only)}
@opindex Wuseless-cast
@opindex Wno-useless-cast
@ -5703,6 +5762,7 @@ Options} and @ref{Objective-C and Objective-C++ Dialect Options}.
-Wunused-label @gol
-Wunused-value @gol
-Wunused-variable @gol
-Wuse-after-free=3 @gol
-Wvla-parameter @r{(C and Objective-C only)} @gol
-Wvolatile-register-var @gol
-Wzero-length-bounds}

View File

@ -53,6 +53,7 @@
#include "stringpool.h"
#include "attribs.h"
#include "demangle.h"
#include "attr-fnspec.h"
#include "pointer-query.h"
/* Return true if tree node X has an associated location. */
@ -2071,6 +2072,7 @@ class pass_waccess : public gimple_opt_pass
opt_pass *clone () { return new pass_waccess (m_ctxt); }
virtual bool gate (function *);
virtual unsigned int execute (function *);
private:
@ -2084,14 +2086,14 @@ private:
/* Check a call to a built-in function. */
bool check_builtin (gcall *);
/* Check a call to an ordinary function. */
bool check_call (gcall *);
/* Check a call to an ordinary function for invalid accesses. */
bool check_call_access (gcall *);
/* Check statements in a basic block. */
void check (basic_block);
void check_block (basic_block);
/* Check a call to a function. */
void check (gcall *);
void check_call (gcall *);
/* Check a call to the named built-in function. */
void check_alloca (gcall *);
@ -2109,10 +2111,27 @@ private:
bool maybe_warn_memmodel (gimple *, tree, tree, const unsigned char *);
void check_atomic_memmodel (gimple *, tree, tree, const unsigned char *);
/* Check for uses of indeterminate pointers. */
void check_pointer_uses (gimple *, tree);
/* Return the argument that a call returns. */
tree gimple_call_return_arg (gcall *);
void warn_invalid_pointer (tree, gimple *, gimple *, bool, bool = false);
/* Return true if use follows an invalidating statement. */
bool use_after_inval_p (gimple *, gimple *);
/* 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;
/* 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;
};
/* Construct the pass. */
@ -2120,7 +2139,9 @@ private:
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_var_cache (),
m_bb_uids_set (),
m_func ()
{
}
@ -3071,6 +3092,15 @@ pass_waccess::check_builtin (gcall *stmt)
check_read_access (stmt, call_arg (stmt, 0));
return true;
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);
}
return true;
case BUILT_IN_GETTEXT:
case BUILT_IN_PUTS:
case BUILT_IN_PUTS_UNLOCKED:
@ -3175,6 +3205,7 @@ pass_waccess::check_builtin (gcall *stmt)
return true;
break;
}
return false;
}
@ -3504,7 +3535,7 @@ pass_waccess::maybe_check_access_sizes (rdwr_map *rwm, tree fndecl, tree fntype,
accesses. Return true if a call has been handled. */
bool
pass_waccess::check_call (gcall *stmt)
pass_waccess::check_call_access (gcall *stmt)
{
tree fntype = gimple_call_fntype (stmt);
if (!fntype)
@ -3692,46 +3723,442 @@ 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. */
bool
pass_waccess::use_after_inval_p (gimple *inval_stmt, gimple *use_stmt)
{
basic_block inval_bb = gimple_bb (inval_stmt);
basic_block use_bb = gimple_bb (use_stmt);
if (inval_bb != use_bb)
return dominated_by_p (CDI_DOMINATORS, use_bb, inval_bb);
if (bitmap_set_bit (m_bb_uids_set, inval_bb->index))
/* The first time this basic block is visited assign increasing ids
to consecutive statements in it. Use the ids to determine which
precedes which. This avoids the linear traversal on subsequent
visits to the same block. */
for (auto si = gsi_start_bb (inval_bb); !gsi_end_p (si);
gsi_next_nondebug (&si))
{
gimple *stmt = gsi_stmt (si);
unsigned uid = inc_gimple_stmt_max_uid (m_func);
gimple_set_uid (stmt, uid);
}
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. */
void
pass_waccess::warn_invalid_pointer (tree ptr, gimple *use_stmt,
gimple *inval_stmt,
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;
location_t use_loc = gimple_location (use_stmt);
if (use_loc == UNKNOWN_LOCATION)
{
use_loc = cfun->function_end_locus;
if (!ptr)
/* Avoid issuing a warning with no context other than
the function. That would make it difficult to debug
in any but very simple cases. */
return;
}
if (is_gimple_call (inval_stmt))
{
if ((equality && warn_use_after_free < 3)
|| (maybe && warn_use_after_free < 2)
|| warning_suppressed_p (use_stmt, OPT_Wuse_after_free))
return;
const tree inval_decl = gimple_call_fndecl (inval_stmt);
if ((ptr && 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,
(maybe
? G_("pointer may be used after %qD")
: G_("pointer used after %qD")),
inval_decl)))
{
location_t loc = gimple_location (inval_stmt);
inform (loc, "call to %qD here", inval_decl);
suppress_warning (use_stmt, OPT_Wuse_after_free);
}
return;
}
}
/* If STMT is a call to either the standard realloc or to a user-defined
reallocation function returns its LHS and set *PTR to the reallocated
pointer. Otherwise return null. */
static tree
get_realloc_lhs (gimple *stmt, tree *ptr)
{
if (gimple_call_builtin_p (stmt, BUILT_IN_REALLOC))
{
*ptr = gimple_call_arg (stmt, 0);
return gimple_call_lhs (stmt);
}
gcall *call = dyn_cast<gcall *>(stmt);
if (!call)
return NULL_TREE;
tree fnattr = NULL_TREE;
tree fndecl = gimple_call_fndecl (call);
if (fndecl)
fnattr = DECL_ATTRIBUTES (fndecl);
else
{
tree fntype = gimple_call_fntype (stmt);
if (!fntype)
return NULL_TREE;
fnattr = TYPE_ATTRIBUTES (fntype);
}
if (!fnattr)
return NULL_TREE;
for (tree ats = fnattr; (ats = lookup_attribute ("*dealloc", ats));
ats = TREE_CHAIN (ats))
{
tree args = TREE_VALUE (ats);
if (!args)
continue;
tree alloc = TREE_VALUE (args);
if (!alloc)
continue;
if (alloc == DECL_NAME (fndecl))
{
unsigned argno = 0;
if (tree index = TREE_CHAIN (args))
argno = TREE_INT_CST_LOW (TREE_VALUE (index)) - 1;
*ptr = gimple_call_arg (stmt, argno);
return gimple_call_lhs (stmt);
}
}
return NULL_TREE;
}
/* Warn if STMT is a call to a deallocation function that's not a match
for the REALLOC_STMT call. Return true if warned. */
static bool
maybe_warn_mismatched_realloc (tree ptr, gimple *realloc_stmt, gimple *stmt)
{
if (!is_gimple_call (stmt))
return false;
tree fndecl = gimple_call_fndecl (stmt);
if (!fndecl)
return false;
unsigned argno = fndecl_dealloc_argno (fndecl);
if (call_nargs (stmt) <= argno)
return false;
if (matching_alloc_calls_p (realloc_stmt, fndecl))
return 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;
location_t loc = gimple_location (stmt);
tree realloc_decl = gimple_call_fndecl (realloc_stmt);
tree dealloc_decl = gimple_call_fndecl (stmt);
if (ptr && !warning_at (loc, OPT_Wmismatched_dealloc,
"%qD called on pointer %qE passed to mismatched "
"allocation function %qD",
dealloc_decl, ptr, realloc_decl))
return false;
if (!ptr && !warning_at (loc, OPT_Wmismatched_dealloc,
"%qD called on a pointer passed to mismatched "
"reallocation function %qD",
dealloc_decl, realloc_decl))
return false;
inform (gimple_location (realloc_stmt),
"call to %qD", realloc_decl);
return true;
}
/* Return true if P and Q point to the same object, and false if they
either don't or their relationship cannot be determined. */
static bool
pointers_related_p (gimple *stmt, tree p, tree q, pointer_query &qry)
{
if (!ptr_derefs_may_alias_p (p, q))
return false;
/* TODO: Work harder to rule out relatedness. */
access_ref pref, qref;
if (!qry.get_ref (p, stmt, &pref, 0)
|| !qry.get_ref (q, stmt, &qref, 0))
return true;
return pref.ref == qref.ref;
}
/* 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). */
void
pass_waccess::check_pointer_uses (gimple *stmt, tree ptr)
{
gcc_assert (TREE_CODE (ptr) == SSA_NAME);
const bool check_dangling = !is_gimple_call (stmt);
basic_block stmt_bb = gimple_bb (stmt);
/* If STMT is a reallocation function set to the reallocated pointer
and the LHS of the call, respectively. */
tree realloc_ptr = NULL_TREE;
tree realloc_lhs = get_realloc_lhs (stmt, &realloc_ptr);
auto_bitmap visited;
auto_vec<tree> pointers;
pointers.safe_push (ptr);
/* Starting with PTR, iterate over POINTERS added by the loop, and
either warn for their uses in basic blocks dominated by the STMT
or in statements that follow it in the same basic block, or add
them to POINTERS if they point into the same object as PTR (i.e.,
are obtained by pointer arithmetic on PTR). */
for (unsigned i = 0; i != pointers.length (); ++i)
{
tree ptr = pointers[i];
if (TREE_CODE (ptr) == SSA_NAME
&& !bitmap_set_bit (visited, SSA_NAME_VERSION (ptr)))
/* Avoid revisiting the same pointer. */
continue;
use_operand_p use_p;
imm_use_iterator iter;
FOR_EACH_IMM_USE_FAST (use_p, iter, ptr)
{
gimple *use_stmt = USE_STMT (use_p);
if (use_stmt == stmt || is_gimple_debug (use_stmt))
continue;
if (realloc_lhs)
{
/* Check to see if USE_STMT is a mismatched deallocation
call for the pointer passed to realloc. That's a bug
regardless of the pointer's value and so warn. */
if (maybe_warn_mismatched_realloc (*use_p->use, stmt, use_stmt))
continue;
/* Pointers passed to realloc that are used in basic blocks
where the realloc call is known to have failed are valid.
Ignore pointers that nothing is known about. Those could
have escaped along with their nullness. */
value_range vr;
if (m_ptr_qry.rvals->range_of_expr (vr, realloc_lhs, use_stmt))
{
if (vr.zero_p ())
continue;
if (!pointers_related_p (stmt, ptr, realloc_ptr, m_ptr_qry))
continue;
}
}
if (check_dangling
&& gimple_code (use_stmt) == GIMPLE_RETURN)
/* Avoid interfering with -Wreturn-local-addr (which runs only
with optimization enabled so it won't diagnose cases that
would be caught here when optimization is disabled). */
continue;
bool equality = false;
if (is_gimple_assign (use_stmt))
{
tree_code code = gimple_assign_rhs_code (use_stmt);
equality = code == EQ_EXPR || code == NE_EXPR;
}
else if (gcond *cond = dyn_cast<gcond *>(use_stmt))
{
tree_code code = gimple_cond_code (cond);
equality = code == EQ_EXPR || code == NE_EXPR;
}
/* 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))
{
/* TODO: Handle PHIs but careful of false positives. */
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;
}
}
if (is_gimple_assign (use_stmt))
{
tree lhs = gimple_assign_lhs (use_stmt);
if (TREE_CODE (lhs) == SSA_NAME)
{
tree_code rhs_code = gimple_assign_rhs_code (use_stmt);
if (rhs_code == POINTER_PLUS_EXPR || rhs_code == SSA_NAME)
pointers.safe_push (lhs);
}
continue;
}
if (gcall *call = dyn_cast <gcall *>(use_stmt))
{
if (gimple_call_return_arg (call))
if (tree lhs = gimple_call_lhs (call))
if (TREE_CODE (lhs) == SSA_NAME)
pointers.safe_push (lhs);
continue;
}
}
}
}
/* Check call STMT for invalid accesses. */
void
pass_waccess::check (gcall *stmt)
pass_waccess::check_call (gcall *stmt)
{
if (gimple_call_builtin_p (stmt, BUILT_IN_NORMAL))
check_builtin (stmt);
if (is_gimple_call (stmt))
check_call (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);
}
}
check_call_access (stmt);
maybe_check_dealloc_call (stmt);
check_nonstring_args (stmt);
}
/* Check basic block BB for invalid accesses. */
void
pass_waccess::check (basic_block bb)
pass_waccess::check_block (basic_block bb)
{
/* Iterate over statements, looking for function calls. */
for (auto si = gsi_start_bb (bb); !gsi_end_p (si); gsi_next (&si))
for (auto si = gsi_start_bb (bb); !gsi_end_p (si);
gsi_next_nondebug (&si))
{
if (gcall *call = dyn_cast <gcall *> (gsi_stmt (si)))
check (call);
gimple *stmt = gsi_stmt (si);
if (gcall *call = dyn_cast <gcall *> (stmt))
check_call (call);
}
}
/* Return 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 (gcall *call)
{
/* Check for attribute fn spec to see if the function returns one
of its arguments. */
attr_fnspec fnspec = gimple_call_fnspec (call);
unsigned int argno;
if (!fnspec.returns_arg (&argno))
{
if (gimple_call_num_args (call) < 1)
return NULL_TREE;
if (!gimple_call_builtin_p (call, BUILT_IN_NORMAL))
return NULL_TREE;
tree fndecl = gimple_call_fndecl (call);
switch (DECL_FUNCTION_CODE (fndecl))
{
case BUILT_IN_MEMPCPY:
case BUILT_IN_MEMPCPY_CHK:
case BUILT_IN_MEMCHR:
case BUILT_IN_STRCHR:
case BUILT_IN_STRRCHR:
case BUILT_IN_STRSTR:
case BUILT_IN_STPCPY:
case BUILT_IN_STPCPY_CHK:
case BUILT_IN_STPNCPY:
case BUILT_IN_STPNCPY_CHK:
argno = 0;
break;
default:
return NULL_TREE;
}
}
if (gimple_call_num_args (call) <= argno)
return NULL_TREE;
return gimple_call_arg (call, argno);
}
/* Check function FUN for invalid accesses. */
unsigned
pass_waccess::execute (function *fun)
{
calculate_dominance_info (CDI_DOMINATORS);
calculate_dominance_info (CDI_POST_DOMINATORS);
/* Create a new ranger instance and associate it with FUN. */
m_ptr_qry.rvals = enable_ranger (fun);
m_func = fun;
auto_bitmap bb_uids_set (&bitmap_default_obstack);
m_bb_uids_set = bb_uids_set;
set_gimple_stmt_max_uid (m_func, 0);
basic_block bb;
FOR_EACH_BB_FN (bb, fun)
check (bb);
check_block (bb);
if (dump_file)
m_ptr_qry.dump (dump_file, (dump_flags & TDF_DETAILS) != 0);
@ -3743,6 +4170,10 @@ pass_waccess::execute (function *fun)
disable_ranger (fun);
m_ptr_qry.rvals = NULL;
m_bb_uids_set = NULL;
free_dominance_info (CDI_POST_DOMINATORS);
free_dominance_info (CDI_DOMINATORS);
return 0;
}

View File

@ -0,0 +1,169 @@
/* Verify that accessing freed objects by built-in functions is diagnosed.
{ dg-do compile }
{ dg-options "-Wall" } */
typedef __SIZE_TYPE__ size_t;
#if __cplusplus
# define EXTERN_C extern "C"
#else
# define EXTERN_C extern
#endif
EXTERN_C void free (void*);
EXTERN_C void* realloc (void*, size_t);
EXTERN_C void* memcpy (void*, const void*, size_t);
EXTERN_C char* strcpy (char*, const char*);
EXTERN_C size_t strlen (const char*);
void sink (void*, ...);
struct Member { char *p; char a[4]; };
int nowarn_strcpy_memptr (struct Member *p)
{
char *q = strcpy (p->p, p->a);
free (p);
return *q;
}
int nowarn_strlen_memptr (struct Member *p)
{
const char *q = p->p;
free (p);
return strlen (q);
}
int warn_strlen_memptr (struct Member *p)
{
free (p); // { dg-message "call to '\(void \)?free\(\\(void\\*\\)\)?'" "note" }
return strlen (p->p); // { dg-warning "-Wuse-after-free" }
}
int warn_strlen_memarray (struct Member *p)
{
{
free (p);
return strlen (p->a); // { dg-warning "-Wuse-after-free" }
}
{
char *q = p->a;
free (p);
return strlen (q); // { dg-warning "-Wuse-after-free" "pr??????" { xfail *-*-* } }
}
}
void* nowarn_realloc_success (void *p)
{
void *q = realloc (p, 7);
if (!q)
/* When realloc fails the original pointer remains valid. */
return p;
return q;
}
void* nowarn_realloc_equal (void *p, int *moved)
{
void *q = realloc (p, 7);
/* Verify that equality is not diagnosed at the default level
(it is diagnosed at level 3). */
*moved = !(p == q);
return q;
}
void* nowarn_realloc_unequal (void *p, int *moved)
{
void *q = realloc (p, 7);
/* Verify that inequality is not diagnosed at the default level
(it is diagnosed at level 3). */
*moved = p != q;
return q;
}
void* warn_realloc_relational (void *p, int *rel)
{
void *q = realloc (p, 7); // { dg-message "call to '\(void\\* \)?realloc\(\\(void\\*, size_t\\)\)?'" "note" }
/* Verify that all relational expressions are diagnosed at the default
level. */
rel[0] = (char*)p < (char*)q; // { dg-warning "-Wuse-after-free" }
rel[1] = (char*)p <= (char*)q; // { dg-warning "-Wuse-after-free" }
rel[2] = (char*)p >= (char*)q; // { dg-warning "-Wuse-after-free" }
rel[3] = (char*)p > (char*)q; // { dg-warning "-Wuse-after-free" }
return q;
}
void* warn_realloc_unchecked (void *p, int *moved)
{
void *q = realloc (p, 7); // { dg-message "call to '\(void\\* \)?realloc\(\\(void\\*, size_t\\)\)?'" "note" }
/* Use subtraction rather than inequality to trigger the warning
at the default level (equality is diagnosed only at level 3). */
*moved = (char*)p - (char*)q; // { dg-warning "-Wuse-after-free" }
return q;
}
void* nowarn_realloc_unchecked_copy (void *p1, void *p2, const void *s,
int n, int *x)
{
void *p3 = memcpy (p1, s, n);
void *p4 = realloc (p2, 7);
*x = p3 != p4;
return p4;
}
void* warn_realloc_unchecked_copy (void *p, const void *s, int n, int *moved)
{
void *p2 = memcpy (p, s, n);
void *q = realloc (p, 7); // { dg-message "call to '\(void\\* \)?realloc\(\\(void\\*, size_t\\)\)?'" "note" }
*moved = (char*)p2 - (char*)q; // { dg-warning "-Wuse-after-free" }
return q;
}
void* warn_realloc_failed (void *p, int *moved)
{
void *q = realloc (p, 7); // { dg-message "call to '\(void\\* \)?realloc\(\\(void\\*, size_t\\)\)?'" "note" }
if (q)
{
/* When realloc succeeds the original pointer is invalid. */
*moved = (char*)p - (char*)q; // { dg-warning "-Wuse-after-free" }
return q;
}
return p;
}
extern void *evp;
void* warn_realloc_extern (void *p, int *moved)
{
evp = realloc (p, 7);
if (evp)
{
/* When realloc succeeds the original pointer is invalid. */
*moved = (char*)p - (char*)evp; // { dg-warning "-Wuse-after-free" "escaped" }
return evp;
}
return p; // { dg-bogus "-Wuse-after-free" "safe use after realloc failure" { xfail *-*-* } }
}
struct A { void *p, *q; int moved; };
void* warn_realloc_arg (struct A *p)
{
p->q = realloc (p->p, 7);
if (p->q)
{
/* When realloc succeeds the original pointer is invalid. */
p->moved = p->p != p->q; // { dg-warning "-Wuse-after-free" "escaped" { xfail *-*-* } }
return p->q;
}
return p->p;
}

View File

@ -0,0 +1,83 @@
/* Exercise -Wuse-after-free with user-defined deallocators.
{ dg-do compile }
{ dg-options "-O0 -Wall" } */
typedef __SIZE_TYPE__ size_t;
#if __cplusplus
# define EXTERN_C extern "C"
#else
# define EXTERN_C extern
#endif
#define A(...) __attribute__ ((malloc (__VA_ARGS__)))
EXTERN_C void free (void *);
EXTERN_C void* realloc (void *, size_t);
typedef struct List { struct List *next; } List;
// User-defined allocator/deallocator just like like realloc and free.
extern void list_free (List *);
extern List* list_realloc (size_t, List *);
extern A (list_realloc, 2) List* list_realloc (size_t, List *);
extern A (list_free, 1) List* list_realloc (size_t, List *);
void sink (void *);
extern int ei;
extern List *elp, *elpa[];
void nowarn_list_free (struct List *lp)
{
{
list_free (lp);
lp = 0;
sink (lp);
}
{
list_free (elp);
elp = 0;
sink (elp);
}
{
list_free (elpa[0]);
elpa[0] = 0;
sink (elpa[0]);
}
{
void *vp = elpa[0];
list_free (elpa[0]);
sink (vp);
}
{
List *p = elpa[1];
if (ei & 1)
list_free (p);
if (ei & 2)
sink (p);
}
{
struct List *next = lp->next;
list_free (lp);
list_free (next);
}
}
void nowarn_list_free_list (List *head)
{
for (List *p = head, *q; p; p = q)
{
q = p->next;
list_free (p);
}
}
void warn_list_free_list (List *head)
{
List *p = head;
for (; p; p = p->next) // { dg-warning "\\\[-Wuse-after-free" }
list_free (p); // { dg-message "call to '\(void \)?list_free\(\\(List\\*\\)\)?'" "note" }
}

View File

@ -0,0 +1,102 @@
/* Verify -Wuse-after-free=1 triggers only for unconditional uses and
not for equality expressions.
{ dg-do compile }
{ dg-options "-O0 -Wall -Wuse-after-free=1" } */
#if __cplusplus
# define EXTERN_C extern "C"
#else
# define EXTERN_C extern
#endif
EXTERN_C void free (void*);
void sink (void*);
void warn_double_free (void *p)
{
free (p);
free (p); // { dg-warning "pointer 'p' used" }
}
void nowarn_cond_double_free (void *p, int c)
{
free (p);
if (c)
free (p);
}
void warn_call_after_free (void *p)
{
free (p);
sink (p); // { dg-warning "pointer 'p' used" }
}
void nowarn_cond_call_after_free (void *p, int c)
{
free (p);
if (c)
sink (p);
}
void* warn_return_after_free (void *p)
{
free (p);
return p; // { dg-warning "pointer 'p' used" }
}
void* nowarn_cond_return_after_free (void *p, int c)
{
free (p);
if (c)
return p;
return 0;
}
void warn_relational_after_free (char *p, char *q[])
{
free (p);
int a[] =
{
p < q[0], // { dg-warning "pointer 'p' used" }
p <= q[1], // { dg-warning "pointer 'p' used" }
p > q[2], // { dg-warning "pointer 'p' used" }
p >= q[3], // { dg-warning "pointer 'p' used" }
p == q[4],
p != q[5]
};
sink (a);
}
void nowarn_cond_relational_after_free (char *p, char *q[], int c)
{
free (p);
int a[] =
{
c ? p < q[0] : q[0][0],
c ? p <= q[1] : q[1][1],
c ? p > q[2] : q[2][2],
c ? p >= q[3] : q[3][3],
c ? p == q[4] : q[4][4],
c ? p != q[5] : q[5][5],
};
sink (a);
}
// Verify no warning for the example in the manual.
struct A { int refcount; void *data; };
void release (struct A *p)
{
int refcount = --p->refcount;
free (p);
if (refcount == 0)
free (p->data); // no warning at level 1
}

View File

@ -0,0 +1,103 @@
/* Verify -Wuse-after-free=2 triggers for conditional as well as
unconditional uses but not for equality expressions.
{ dg-do compile }
{ dg-options "-O0 -Wall -Wuse-after-free=2" } */
#if __cplusplus
# define EXTERN_C extern "C"
#else
# define EXTERN_C extern
#endif
EXTERN_C void free (void*);
void sink (void*);
void warn_double_free (void *p)
{
free (p);
free (p); // { dg-warning "pointer 'p' used" }
}
void warn_cond_double_free (void *p, int c)
{
free (p);
if (c)
free (p); // { dg-warning "pointer 'p' may be used" }
}
void warn_call_after_free (void *p)
{
free (p);
sink (p); // { dg-warning "pointer 'p' used" }
}
void warn_cond_call_after_free (void *p, int c)
{
free (p);
if (c)
sink (p); // { dg-warning "pointer 'p' may be used" }
}
void* warn_return_after_free (void *p)
{
free (p);
return p; // { dg-warning "pointer 'p' used" }
}
void* warn_cond_return_after_free (void *p, int c)
{
free (p);
if (c)
return p; // { dg-warning "pointer 'p' may be used" }
return 0;
}
void warn_relational_after_free (char *p, char *q[])
{
free (p);
int a[] =
{
p < q[0], // { dg-warning "pointer 'p' used" }
p <= q[1], // { dg-warning "pointer 'p' used" }
p > q[2], // { dg-warning "pointer 'p' used" }
p >= q[3], // { dg-warning "pointer 'p' used" }
p == q[4],
p != q[5]
};
sink (a);
}
void warn_cond_relational_after_free (char *p, char *q[], int c)
{
free (p);
int a[] =
{
c ? p < q[0] : q[0][0], // { dg-warning "pointer 'p' may be used" }
c ? p <= q[1] : q[1][1], // { dg-warning "pointer 'p' may be used" }
c ? p > q[2] : q[2][2], // { dg-warning "pointer 'p' may be used" }
c ? p >= q[3] : q[3][3], // { dg-warning "pointer 'p' may be used" }
c ? p == q[4] : q[4][4],
c ? p != q[5] : q[5][5],
};
sink (a);
}
// Verify warning for the example in the manual.
struct A { int refcount; void *data; };
void release (struct A *p)
{
int refcount = --p->refcount;
free (p);
if (refcount == 0)
free (p->data); // { dg-warning "pointer 'p' may be used" }
}

View File

@ -0,0 +1,105 @@
/* Verify -Wuse-after-free=2 triggers for conditional as well as
unconditional uses but not for equality expressions. Same as
-Wuse-after-free-5.c but with optimization.
{ dg-do compile }
{ dg-options "-O2 -Wall -Wuse-after-free=2" } */
#if __cplusplus
# define EXTERN_C extern "C"
#else
# define EXTERN_C extern
#endif
EXTERN_C void free (void*);
void sink (void*);
void warn_double_free (void *p)
{
free (p);
free (p); // { dg-warning "pointer 'p' used" }
}
void warn_cond_double_free (void *p, int c)
{
free (p);
if (c)
free (p); // { dg-warning "pointer 'p' may be used" }
}
void warn_call_after_free (void *p)
{
free (p);
sink (p); // { dg-warning "pointer 'p' used" }
}
void warn_cond_call_after_free (void *p, int c)
{
free (p);
if (c)
sink (p); // { dg-warning "pointer 'p' may be used" }
}
void* warn_return_after_free (void *p)
{
free (p);
return p; // { dg-warning "pointer 'p' used" }
}
void* warn_cond_return_after_free (void *p, int c)
{
free (p);
// PHI handling not fully implemented.
if (c)
return p; // { dg-warning "pointer 'p' may be used" "pr??????" { xfail *-*-* } }
return 0;
}
void warn_relational_after_free (char *p, char *q[])
{
free (p);
int a[] =
{
p < q[0], // { dg-warning "pointer 'p' used" }
p <= q[1], // { dg-warning "pointer 'p' used" }
p > q[2], // { dg-warning "pointer 'p' used" }
p >= q[3], // { dg-warning "pointer 'p' used" }
p == q[4],
p != q[5]
};
sink (a);
}
void warn_cond_relational_after_free (char *p, char *q[], int c)
{
free (p);
int a[] =
{
c ? p < q[0] : q[0][0], // { dg-warning "pointer 'p' may be used" }
c ? p <= q[1] : q[1][1], // { dg-warning "pointer 'p' may be used" }
c ? p > q[2] : q[2][2], // { dg-warning "pointer 'p' may be used" }
c ? p >= q[3] : q[3][3], // { dg-warning "pointer 'p' may be used" }
c ? p == q[4] : q[4][4],
c ? p != q[5] : q[5][5],
};
sink (a);
}
// Verify warning for the example in the manual.
struct A { int refcount; void *data; };
void release (struct A *p)
{
int refcount = --p->refcount;
free (p);
if (refcount == 0)
free (p->data); // { dg-warning "pointer 'p' may be used" }
}

View File

@ -0,0 +1,103 @@
/* Verify -Wuse-after-free=3 triggers for conditional and unconditional
uses in all expressions including equality.
{ dg-do compile }
{ dg-options "-O0 -Wall -Wuse-after-free=3" } */
#if __cplusplus
# define EXTERN_C extern "C"
#else
# define EXTERN_C extern
#endif
EXTERN_C void free (void*);
void sink (void*);
void warn_double_free (void *p)
{
free (p);
free (p); // { dg-warning "pointer 'p' used" }
}
void warn_cond_double_free (void *p, int c)
{
free (p);
if (c)
free (p); // { dg-warning "pointer 'p' may be used" }
}
void warn_call_after_free (void *p)
{
free (p);
sink (p); // { dg-warning "pointer 'p' used" }
}
void warn_cond_call_after_free (void *p, int c)
{
free (p);
if (c)
sink (p); // { dg-warning "pointer 'p' may be used" }
}
void* warn_return_after_free (void *p)
{
free (p);
return p; // { dg-warning "pointer 'p' used" }
}
void* warn_cond_return_after_free (void *p, int c)
{
free (p);
if (c)
return p; // { dg-warning "pointer 'p' may be used" }
return 0;
}
void warn_relational_after_free (char *p, char *q[])
{
free (p);
int a[] =
{
p < q[0], // { dg-warning "pointer 'p' used" }
p <= q[1], // { dg-warning "pointer 'p' used" }
p > q[2], // { dg-warning "pointer 'p' used" }
p >= q[3], // { dg-warning "pointer 'p' used" }
p == q[4], // { dg-warning "pointer 'p' used" }
p != q[5] // { dg-warning "pointer 'p' used" }
};
sink (a);
}
void warn_cond_relational_after_free (char *p, char *q[], int c)
{
free (p);
int a[] =
{
c ? p < q[0] : q[0][0], // { dg-warning "pointer 'p' may be used" }
c ? p <= q[1] : q[1][1], // { dg-warning "pointer 'p' may be used" }
c ? p > q[2] : q[2][2], // { dg-warning "pointer 'p' may be used" }
c ? p >= q[3] : q[3][3], // { dg-warning "pointer 'p' may be used" }
c ? p == q[4] : q[4][4], // { dg-warning "pointer 'p' may be used" }
c ? p != q[5] : q[5][5], // { dg-warning "pointer 'p' may be used" }
};
sink (a);
}
// Verify warning for the example in the manual.
struct A { int refcount; void *data; };
void release (struct A *p)
{
int refcount = --p->refcount;
free (p);
if (refcount == 0)
free (p->data); // { dg-warning "pointer 'p' may be used" }
}

View File

@ -0,0 +1,167 @@
/* Exercise basic cases of -Wuse-after-free without optimization.
{ dg-do compile }
{ dg-options "-O0 -Wall" } */
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* calloc (size_t, size_t);
EXTERN_C void* malloc (size_t);
EXTERN_C void free (void*);
void sink (void *);
extern void* evp;
extern void* evpa[];
extern int ei;
struct List { struct List *next; };
void nowarn_free (void *vp, struct List *lp)
{
{
free (vp);
vp = 0;
sink (vp);
}
{
free (evp);
evp = 0;
sink (evp);
}
{
free (evpa[0]);
evpa[0] = 0;
sink (evpa[0]);
}
{
void *vp = evpa[0];
free (evpa[1]);
sink (vp);
}
{
void *p = evpa[1];
if (ei & 1)
free (p);
if (ei & 2)
sink (p);
}
{
struct List *next = lp->next;
free (lp);
free (next);
}
}
void nowarn_free_arg (void *p, void *q)
{
free (p);
if (q)
free (q);
}
void nowarn_free_extern (void)
{
extern void *ep, *eq;
free (ep);
ep = eq;
free (ep);
}
void nowarn_free_assign (void)
{
extern void *ep;
free (ep);
ep = 0;
free (ep);
}
#pragma GCC diagnostic push
/* Verify that -Wuse-after-free works with #pragma diagnostic. Note
that the option name should not need to include a trailing =, even
though it's a multi-level option. (specifying the level after
the option, as in "-Wuse-after-free=2", doesn't work. */
#pragma GCC diagnostic ignored "-Wuse-after-free"
void nowarn_double_free_suppressed (void *p)
{
free (p);
free (p);
}
#pragma GCC diagnostic pop
void warn_double_free_arg (void *p)
{
free (p); // { dg-message "call to '\(void \)?free\(\\(void\\*\\)\)?'" "note" }
// Verify exactly one warning is issued.
free (p); // { dg-warning "\\\-Wuse-after-free" }
// { dg-bogus "\\\-Wuse-after-free" "duplicate warning" { target *-*-* } .-1 }
}
void warn_double_free_extern (void)
{
/* GCC assumes free() clobbers global memory and the warning is
too simplistic to see through that assumption. */
extern void *ep, *eq;
{
eq = ep;
free (ep); // { dg-message "call to 'free'" "pr??????" { xfail *-*-* } }
free (eq); // { dg-warning "\\\-Wuse-after-free" "pr??????" { xfail *-*-* } }
}
}
void warn_deref_after_free (int *p, int i)
{
int *q0 = p, *q1 = p + 1, *qi = p + i;
free (p); // { dg-message "call to '\(void \)?free\(\\(void\\*\\)\)?'" "note" }
*p = 0; // { dg-warning "\\\-Wuse-after-free" }
*q0 = 0; // { dg-warning "\\\-Wuse-after-free" }
*q1 = 0; // { dg-warning "\\\-Wuse-after-free" }
*qi = 0; // { dg-warning "\\\-Wuse-after-free" }
}
void warn_array_ref_after_free (int *p, int i)
{
free (p); // { dg-message "call to '\(void \)?free\(\\(void\\*\\)\)?'" "note" }
p[i] = 0; // { dg-warning "\\\-Wuse-after-free" }
}
void nowarn_free_list (struct List *head)
{
for (struct List *p = head, *q; p; p = q)
{
q = p->next;
free (p);
}
}
void warn_free_list (struct List *head)
{
struct List *p = head;
for (; p; p = p->next) // { dg-warning "\\\[-Wuse-after-free" }
free (p); // { dg-message "call to '\(void \)?free\(\\(void\\*\\)\)?'" "note" }
}
void warn_free (void *vp)
{
{
free (vp); // { dg-message "call to '\(void \)?free\(\\(void\\*\\)\)?'" "note" }
evp = vp; // { dg-warning "-Wuse-after-free" }
evpa[0] = vp; // { dg-warning "-Wuse-after-free" }
evpa[1] = evp;
}
}

View File

@ -0,0 +1,70 @@
/* Verify that passing a pointer to a deallocation function that was
previously passed to a mismatched reallocation function is diagnosed
by -Wmismatched-dealloc (and not by some other warning).
{ dg-do compile }
{ dg-options "-Wall" } */
#define A(...) __attribute__ ((malloc (__VA_ARGS__)))
typedef __SIZE_TYPE__ size_t;
extern "C"
{
void free (void *);
void* realloc (void *, size_t);
}
// User-defined allocator/deallocator just like like realloc.
int* int_realloc (size_t, int *);
A (int_realloc, 2) int* int_realloc (size_t, int *);
void sink (void *);
void* warn_realloc_op_delete (void *p)
{
void *q = realloc (p, 5); // { dg-message "call to 'void\\* realloc\\(void\\*, size_t\\)'" "note" }
operator delete (p); // { dg-warning "'void operator delete\\(void\\*\\)' called on pointer 'p' passed to mismatched allocation function 'void\\* realloc\\(void\\*, size_t\\)' \\\[-Wmismatched-dealloc" }
return q;
}
void* warn_realloc_op_delete_cond (void *p)
{
void *q = realloc (p, 5); // { dg-message "call to 'void\\* realloc\\(void\\*, size_t\\)'" "note" }
if (!q)
operator delete (p); // { dg-warning "'void operator delete\\(void\\*\\)' called on pointer 'p' passed to mismatched allocation function 'void\\* realloc\\(void\\*, size_t\\)'" }
return q;
}
void* warn_realloc_array_delete_char (char *p)
{
char *q;
q = (char*)realloc (p, 7); // { dg-message "call to 'void\\* realloc\\(void\\*, size_t\\)'" "note" }
if (!q)
delete[] (p); // { dg-warning "'void operator delete \\\[]\\(void\\*\\)' called on pointer 'p' passed to mismatched allocation function 'void\\* realloc\\(void\\*, size_t\\)'" }
return q;
}
int* warn_int_realloc_op_delete (int *p)
{
int *q;
q = int_realloc (5, p); // { dg-message "call to 'int\\* int_realloc\\(size_t, int\\*\\)'" "note" }
operator delete (p); // { dg-warning "'void operator delete\\(void\\*\\)' called on pointer 'p' passed to mismatched allocation function 'int\\* int_realloc\\(size_t, int\\*\\)' \\\[-Wmismatched-dealloc" }
return q;
}
int* warn_int_realloc_free (int *p)
{
int *q;
q = int_realloc (5, p); // { dg-message "call to 'int\\* int_realloc\\(size_t, int\\*\\)'" "note" }
free (p); // { dg-warning "'void free\\(void\\*\\)' called on pointer 'p' passed to mismatched allocation function 'int\\* int_realloc\\(size_t, int\\*\\)' \\\[-Wmismatched-dealloc" }
return q;
}

View File

@ -0,0 +1,158 @@
/* Exercise basic C++ only cases of -Wuse-after-free without optimization.
{ dg-do compile }
{ dg-options "-O0 -Wall" } */
typedef __SIZE_TYPE__ size_t;
extern "C" void free (void *);
extern "C" void* realloc (void *, size_t);
void sink (void *);
extern void* evp;
extern void* evpa[];
extern int ei;
struct List { struct List *next; };
void nowarn_delete (void *vp, struct List *lp)
{
{
operator delete (vp);
vp = 0;
sink (vp);
}
{
operator delete (evp);
evp = 0;
sink (evp);
}
{
operator delete (evpa[0]);
evpa[0] = 0;
sink (evpa[0]);
}
{
void *vp = evpa[0];
operator delete (evpa[0]);
sink (vp);
}
{
void *p = evpa[1];
if (ei & 1)
operator delete (p);
if (ei & 2)
sink (p);
}
{
struct List *next = lp->next;
operator delete (lp);
operator delete (next);
}
}
void nowarn_delete_arg (void *p, void *q)
{
operator delete (p);
if (q)
operator delete (q);
}
void nowarn_delete_extern (void)
{
extern void *ep, *eq;
operator delete (ep);
ep = eq;
operator delete (ep);
}
void nowarn_delete_assign (void)
{
extern void *ep;
operator delete (ep);
ep = 0;
operator delete (ep);
}
void warn_double_delete_arg (void *p)
{
operator delete (p); // { dg-message "call to 'void operator delete\\(void\\*\\)'" "note" }
operator delete (p); // { dg-warning "\\\-Wuse-after-free" }
}
void warn_delete_free_arg (void *p)
{
operator delete (p); // { dg-message "call to 'void operator delete\\(void\\*\\)'" "note" }
free (p); // { dg-warning "\\\-Wuse-after-free" }
}
void warn_free_delete_arg (void *p)
{
free (p); // { dg-message "call to 'void free\\(void\\*\\)'" "note" }
operator delete (p); // { dg-warning "\\\-Wuse-after-free" }
}
void warn_mismatched_double_delete_arg (void *p, void *q)
{
operator delete (p); // { dg-message "call to 'void operator delete\\(void\\*\\)'" "note" }
operator delete[] (p); // { dg-warning "\\\-Wuse-after-free" }
operator delete[] (q); // { dg-message "call to 'void operator delete \\\[]\\(void\\*\\)'" "note" }
operator delete (q); // { dg-warning "\\\-Wuse-after-free" }
}
void warn_double_delete_extern (void)
{
/* GCC assumes operator delete() clobbers global memory and the warning is
too simplistic to see through that assumption. */
extern void *ep, *eq;
{
eq = ep;
operator delete (ep); // { dg-message "call to 'operator delete'" "pr??????" { xfail *-*-* } }
operator delete (eq); // { dg-warning "\\\-Wuse-after-free" "pr??????" { xfail *-*-* } }
}
}
void warn_deref_after_delete (int *p, int i)
{
int *q0 = p, *q1 = p + 1, *qi = p + i;
operator delete (p); // { dg-message "call to 'void operator delete\\(void\\*\\)'" "note" }
*p = 0; // { dg-warning "\\\-Wuse-after-free" }
*q0 = 0; // { dg-warning "\\\-Wuse-after-free" }
*q1 = 0; // { dg-warning "\\\-Wuse-after-free" }
*qi = 0; // { dg-warning "\\\-Wuse-after-free" }
}
void warn_array_ref_after_delete (int *p, int i)
{
operator delete (p); // { dg-message "call to 'void operator delete\\(void\\*\\)'" "note" }
p[i] = 0; // { dg-warning "\\\-Wuse-after-free" }
}
void nowarn_delete_list (struct List *head)
{
for (struct List *p = head, *q; p; p = q)
{
q = p->next;
operator delete (p);
}
}
void warn_delete_list (struct List *head)
{
struct List *p = head;
for (; p; p = p->next) // { dg-warning "\\\[-Wuse-after-free" }
operator delete (p); // { dg-message "call to 'void operator delete\\(void\\*\\)'" "note" }
}
void warn_delete (void *vp)
{
{
operator delete (vp); // { dg-message "call to 'void operator delete\\(void\\*\\)'" "note" }
evp = vp; // { dg-warning "-Wuse-after-free" }
evpa[0] = vp; // { dg-warning "-Wuse-after-free" }
evpa[1] = evp;
}
}

View File

@ -26,6 +26,7 @@ void dealloc (void*);
A (dealloc) void* alloc (int);
void sink (void*);
void* source (void);
void test_alloc_A (void)
{
@ -107,35 +108,35 @@ void test_realloc_A (void *ptr)
}
void test_realloc (void *ptr)
void test_realloc (void)
{
extern void free (void*);
extern void* realloc (void*, size_t);
{
void *p = realloc (ptr, 1);
void *p = realloc (source (), 1);
p = realloc_A (p, 2);
__builtin_free (p);
}
{
void *p = realloc (ptr, 2);
void *p = realloc (source (), 2);
p = realloc_A (p, 2);
free (p);
}
{
void *p = realloc (ptr, 3);
void *p = realloc (source (), 3);
free (p);
}
{
void *p = realloc (ptr, 4);
void *p = realloc (source (), 4);
__builtin_free (p);
}
{
void *p = realloc (ptr, 5); // { dg-message "returned from 'realloc'" }
void *p = realloc (source (), 5); // { dg-message "returned from 'realloc'" }
dealloc (p); // { dg-warning "'dealloc' called on pointer returned from a mismatched allocation function" }
}
}

View File

@ -157,6 +157,7 @@ void test_reallocarray (void *p)
}
{
p = source ();
void *q = realloc (p, 1);
q = reallocarray (q, 2, 3);
sink (q);
@ -192,6 +193,7 @@ void test_reallocarray (void *p)
}
{
p = source ();
void *q = reallocarray (p, 7, 8);
q = __builtin_realloc (q, 9);
sink (q);
@ -199,6 +201,7 @@ void test_reallocarray (void *p)
}
{
p = source ();
void *q = reallocarray (p, 7, 8);
q = realloc (q, 9);
sink (q);
@ -206,6 +209,7 @@ void test_reallocarray (void *p)
}
{
p = source ();
void *q = reallocarray (p, 8, 9);
q = reallocarray (q, 3, 4);
sink (q);
@ -213,6 +217,7 @@ void test_reallocarray (void *p)
}
{
p = source ();
void *q = reallocarray (p, 9, 10);
q = reallocarray (q, 3, 4);
sink (q);

View File

@ -13,6 +13,9 @@ test_1 (const char *path)
/* { dg-message "second 'fclose' here; first 'fclose' was at \\(5\\)" "second fclose" { target *-*-* } .-1 } */
}
/* Swallow -Wuse-after-free issued for the same problem
{ dg-prune-output "-Wuse-after-free" } */
void
test_2 (const char *src, const char *dst)
{

View File

@ -16,3 +16,6 @@ void test (const char *path)
fclose (f.m_f);
fclose (f.m_f); /* { dg-warning "double 'fclose' of FILE 'f.m_f'" } */
}
/* Swallow -Wuse-after-free issued for the same problem
{ dg-prune-output "-Wuse-after-free" } */

View File

@ -5,7 +5,7 @@
-Walloc-larger-than=maximum. */
/* { dg-do compile } */
/* { dg-require-effective-target alloca } */
/* { dg-options "-O0 -Wall -Walloc-size-larger-than=12345" } */
/* { dg-options "-O0 -Wall -Walloc-size-larger-than=12345 -Wno-use-after-free" } */
#define MAXOBJSZ 12345

View File

@ -4,7 +4,7 @@
of the maximum specified by -Walloc-size-larger-than=maximum. */
/* { dg-do compile } */
/* { dg-require-effective-target alloca } */
/* { dg-options "-O1 -Wall -Walloc-size-larger-than=12345" } */
/* { dg-options "-O1 -Wall -Walloc-size-larger-than=12345 -Wno-use-after-free" } */
#define SIZE_MAX __SIZE_MAX__
#define MAXOBJSZ 12345

View File

@ -553,12 +553,11 @@ _cpp_find_file (cpp_reader *pfile, const char *fname, cpp_dir *start_dir,
{
/* If *hash_slot is NULL, the above
htab_find_slot_with_hash call just created the
slot, but we aren't going to store there
anything, so need to remove the newly created
entry. htab_clear_slot requires that it is
non-NULL, so store there some non-NULL pointer,
htab_clear_slot will overwrite it
immediately. */
slot, but we aren't going to store there anything
of use, so need to remove the newly created entry.
htab_clear_slot requires that it is non-NULL, so
store some non-NULL but valid pointer there,
htab_clear_slot will immediately overwrite it. */
*hash_slot = file;
htab_clear_slot (pfile->file_hash, hash_slot);
}
@ -582,7 +581,7 @@ _cpp_find_file (cpp_reader *pfile, const char *fname, cpp_dir *start_dir,
if (*hash_slot == NULL)
{
/* See comment on the above htab_clear_slot call. */
*hash_slot = file;
*hash_slot = &hash_slot;
htab_clear_slot (pfile->file_hash, hash_slot);
}
return NULL;

View File

@ -30,6 +30,10 @@
#pragma alloca
#endif
#if __GNUC__ >= 12
# pragma GCC diagnostic ignored "-Wuse-after-free"
#endif
#undef _GNU_SOURCE
#define _GNU_SOURCE