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:
parent
29401b7b45
commit
671a283636
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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 () { }
|
||||
|
@ -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}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
169
gcc/testsuite/c-c++-common/Wuse-after-free-2.c
Normal file
169
gcc/testsuite/c-c++-common/Wuse-after-free-2.c
Normal 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;
|
||||
}
|
83
gcc/testsuite/c-c++-common/Wuse-after-free-3.c
Normal file
83
gcc/testsuite/c-c++-common/Wuse-after-free-3.c
Normal 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" }
|
||||
}
|
102
gcc/testsuite/c-c++-common/Wuse-after-free-4.c
Normal file
102
gcc/testsuite/c-c++-common/Wuse-after-free-4.c
Normal 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
|
||||
}
|
103
gcc/testsuite/c-c++-common/Wuse-after-free-5.c
Normal file
103
gcc/testsuite/c-c++-common/Wuse-after-free-5.c
Normal 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" }
|
||||
}
|
105
gcc/testsuite/c-c++-common/Wuse-after-free-6.c
Normal file
105
gcc/testsuite/c-c++-common/Wuse-after-free-6.c
Normal 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" }
|
||||
}
|
103
gcc/testsuite/c-c++-common/Wuse-after-free-7.c
Normal file
103
gcc/testsuite/c-c++-common/Wuse-after-free-7.c
Normal 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" }
|
||||
}
|
167
gcc/testsuite/c-c++-common/Wuse-after-free.c
Normal file
167
gcc/testsuite/c-c++-common/Wuse-after-free.c
Normal 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;
|
||||
}
|
||||
}
|
70
gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-3.C
Normal file
70
gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-3.C
Normal 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;
|
||||
}
|
158
gcc/testsuite/g++.dg/warn/Wuse-after-free.C
Normal file
158
gcc/testsuite/g++.dg/warn/Wuse-after-free.C
Normal 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;
|
||||
}
|
||||
}
|
@ -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" }
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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" } */
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -30,6 +30,10 @@
|
||||
#pragma alloca
|
||||
#endif
|
||||
|
||||
#if __GNUC__ >= 12
|
||||
# pragma GCC diagnostic ignored "-Wuse-after-free"
|
||||
#endif
|
||||
|
||||
#undef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user