analyzer: generalize sm-malloc to new/delete [PR94355]
This patch generalizes the state machine in sm-malloc.c to support multiple allocator APIs, and adds just enough support for C++ new and delete to demonstrate the feature, allowing for detection of code paths where the result of new in C++ can leak - for some crude examples, at least (bearing in mind that the analyzer doesn't yet know about e.g. vfuncs, exceptions, inheritance, RTTI, etc) It also implements a new warning: -Wanalyzer-mismatching-deallocation. For example: demo.cc: In function 'void test()': demo.cc:8:8: warning: 'f' should have been deallocated with 'delete' but was deallocated with 'free' [CWE-762] [-Wanalyzer-mismatching-deallocation] 8 | free (f); | ~~~~~^~~ 'void test()': events 1-2 | | 7 | foo *f = new foo; | | ^~~ | | | | | (1) allocated here (expects deallocation with 'delete') | 8 | free (f); | | ~~~~~~~~ | | | | | (2) deallocated with 'free' here; allocation at (1) expects deallocation with 'delete' | The patch also adds just enough knowledge of exception-handling to suppress a false positive from -Wanalyzer-malloc-leak on g++.dg/analyzer/pr96723.C on the exception-handling CFG edge after operator new. It does this by adding a constraint that the result is NULL if an exception was thrown from operator new, since the result from operator new is lost when following that exception-handling CFG edge. gcc/analyzer/ChangeLog: PR analyzer/94355 * analyzer.opt (Wanalyzer-mismatching-deallocation): New warning. * region-model-impl-calls.cc (region_model::impl_call_operator_new): New. (region_model::impl_call_operator_delete): New. * region-model.cc (region_model::on_call_pre): Detect operator new and operator delete. (region_model::on_call_post): Likewise. (region_model::maybe_update_for_edge): Detect EH edges and call... (region_model::apply_constraints_for_exception): New function. * region-model.h (region_model::impl_call_operator_new): New decl. (region_model::impl_call_operator_delete): New decl. (region_model::apply_constraints_for_exception): New decl. * sm-malloc.cc (enum resource_state): New. (struct allocation_state): New state subclass. (enum wording): New. (struct api): New. (malloc_state_machine::custom_data_t): New typedef. (malloc_state_machine::add_state): New decl. (malloc_state_machine::m_unchecked) (malloc_state_machine::m_nonnull) (malloc_state_machine::m_freed): Delete these states in favor of... (malloc_state_machine::m_malloc) (malloc_state_machine::m_scalar_new) (malloc_state_machine::m_vector_new): ...this new api instances, which own their own versions of these states. (malloc_state_machine::on_allocator_call): New decl. (malloc_state_machine::on_deallocator_call): New decl. (api::api): New ctor. (dyn_cast_allocation_state): New. (as_a_allocation_state): New. (get_rs): New. (unchecked_p): New. (nonnull_p): New. (freed_p): New. (malloc_diagnostic::describe_state_change): Use unchecked_p and nonnull_p. (class mismatching_deallocation): New. (double_free::double_free): Add funcname param for initializing m_funcname. (double_free::emit): Use m_funcname in warning message rather than hardcoding "free". (double_free::describe_state_change): Likewise. Use freed_p. (double_free::describe_call_with_state): Use freed_p. (double_free::describe_final_event): Use m_funcname in message rather than hardcoding "free". (double_free::m_funcname): New field. (possible_null::describe_state_change): Use unchecked_p. (possible_null::describe_return_of_state): Likewise. (use_after_free::use_after_free): Add param for initializing m_api. (use_after_free::emit): Use m_api->m_dealloc_funcname in message rather than hardcoding "free". (use_after_free::describe_state_change): Use freed_p. Change the wording of the message based on the API. (use_after_free::describe_final_event): Use m_api->m_dealloc_funcname in message rather than hardcoding "free". Change the wording of the message based on the API. (use_after_free::m_api): New field. (malloc_leak::describe_state_change): Use unchecked_p. Update for renaming of m_malloc_event to m_alloc_event. (malloc_leak::describe_final_event): Update for renaming of m_malloc_event to m_alloc_event. (malloc_leak::m_malloc_event): Rename... (malloc_leak::m_alloc_event): ...to this. (free_of_non_heap::free_of_non_heap): Add param for initializing m_funcname. (free_of_non_heap::emit): Use m_funcname in message rather than hardcoding "free". (free_of_non_heap::describe_final_event): Likewise. (free_of_non_heap::m_funcname): New field. (allocation_state::dump_to_pp): New. (allocation_state::get_nonnull): New. (malloc_state_machine::malloc_state_machine): Update for changes to state fields and new api fields. (malloc_state_machine::add_state): New. (malloc_state_machine::on_stmt): Move malloc/calloc handling to on_allocator_call and call it, passing in the API pointer. Likewise for free, moving it to on_deallocator_call. Handle calls to operator new and delete in an analogous way. Use unchecked_p when testing for possibly-null-arg and possibly-null-deref, and transition to the non-null for the correct API. Remove redundant node param from call to on_zero_assignment. Use freed_p for use-after-free check, and pass in API. (malloc_state_machine::on_allocator_call): New, based on code in on_stmt. (malloc_state_machine::on_deallocator_call): Likewise. (malloc_state_machine::on_phi): Mark node param with ATTRIBUTE_UNUSED; don't pass it to on_zero_assignment. (malloc_state_machine::on_condition): Mark node param with ATTRIBUTE_UNUSED. Replace on_transition calls with get_state and set_next_state pairs, transitioning to the non-null state for the appropriate API. (malloc_state_machine::can_purge_p): Port to new state approach. (malloc_state_machine::on_zero_assignment): Replace on_transition calls with get_state and set_next_state pairs. Drop redundant node param. * sm.h (state_machine::add_custom_state): New. gcc/ChangeLog: PR analyzer/94355 * doc/invoke.texi: Document -Wanalyzer-mismatching-deallocation. gcc/testsuite/ChangeLog: PR analyzer/94355 * g++.dg/analyzer/new-1.C: New test. * g++.dg/analyzer/new-vs-malloc.C: New test.
This commit is contained in:
parent
fcf56ef5d5
commit
1690a839cf
9 changed files with 674 additions and 114 deletions
|
@ -70,6 +70,10 @@ Wanalyzer-malloc-leak
|
|||
Common Var(warn_analyzer_malloc_leak) Init(1) Warning
|
||||
Warn about code paths in which a heap-allocated pointer leaks.
|
||||
|
||||
Wanalyzer-mismatching-deallocation
|
||||
Common Var(warn_analyzer_mismatching_deallocation) Init(1) Warning
|
||||
Warn about code paths in which the wrong deallocation function is called.
|
||||
|
||||
Wanalyzer-possible-null-argument
|
||||
Common Var(warn_analyzer_possible_null_argument) Init(1) Warning
|
||||
Warn about code paths in which a possibly-NULL value is passed to a must-not-be-NULL function argument.
|
||||
|
|
|
@ -318,6 +318,41 @@ region_model::impl_call_memset (const call_details &cd)
|
|||
return false;
|
||||
}
|
||||
|
||||
/* Handle the on_call_pre part of "operator new". */
|
||||
|
||||
bool
|
||||
region_model::impl_call_operator_new (const call_details &cd)
|
||||
{
|
||||
const svalue *size_sval = cd.get_arg_svalue (0);
|
||||
const region *new_reg = create_region_for_heap_alloc (size_sval);
|
||||
if (cd.get_lhs_type ())
|
||||
{
|
||||
const svalue *ptr_sval
|
||||
= m_mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg);
|
||||
cd.maybe_set_lhs (ptr_sval);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Handle the on_call_pre part of "operator delete", which comes in
|
||||
both sized and unsized variants (2 arguments and 1 argument
|
||||
respectively). */
|
||||
|
||||
bool
|
||||
region_model::impl_call_operator_delete (const call_details &cd)
|
||||
{
|
||||
const svalue *ptr_sval = cd.get_arg_svalue (0);
|
||||
if (const region_svalue *ptr_to_region_sval
|
||||
= ptr_sval->dyn_cast_region_svalue ())
|
||||
{
|
||||
/* If the ptr points to an underlying heap region, delete it,
|
||||
poisoning pointers. */
|
||||
const region *freed_reg = ptr_to_region_sval->get_pointee ();
|
||||
unbind_region_and_descendents (freed_reg, POISON_KIND_FREED);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Handle the on_call_pre part of "strlen".
|
||||
Return true if the LHS is updated. */
|
||||
|
||||
|
|
|
@ -706,6 +706,16 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt)
|
|||
if (impl_call_strlen (cd))
|
||||
return false;
|
||||
}
|
||||
else if (is_named_call_p (callee_fndecl, "operator new", call, 1))
|
||||
return impl_call_operator_new (cd);
|
||||
else if (is_named_call_p (callee_fndecl, "operator new []", call, 1))
|
||||
return impl_call_operator_new (cd);
|
||||
else if (is_named_call_p (callee_fndecl, "operator delete", call, 1)
|
||||
|| is_named_call_p (callee_fndecl, "operator delete", call, 2)
|
||||
|| is_named_call_p (callee_fndecl, "operator delete []", call, 1))
|
||||
{
|
||||
/* Handle in "on_call_post". */
|
||||
}
|
||||
else if (!fndecl_has_gimple_body_p (callee_fndecl)
|
||||
&& !DECL_PURE_P (callee_fndecl)
|
||||
&& !fndecl_built_in_p (callee_fndecl))
|
||||
|
@ -746,12 +756,22 @@ region_model::on_call_post (const gcall *call,
|
|||
region_model_context *ctxt)
|
||||
{
|
||||
if (tree callee_fndecl = get_fndecl_for_call (call, ctxt))
|
||||
if (is_named_call_p (callee_fndecl, "free", call, 1))
|
||||
{
|
||||
call_details cd (call, this, ctxt);
|
||||
impl_call_free (cd);
|
||||
return;
|
||||
}
|
||||
{
|
||||
if (is_named_call_p (callee_fndecl, "free", call, 1))
|
||||
{
|
||||
call_details cd (call, this, ctxt);
|
||||
impl_call_free (cd);
|
||||
return;
|
||||
}
|
||||
if (is_named_call_p (callee_fndecl, "operator delete", call, 1)
|
||||
|| is_named_call_p (callee_fndecl, "operator delete", call, 2)
|
||||
|| is_named_call_p (callee_fndecl, "operator delete []", call, 1))
|
||||
{
|
||||
call_details cd (call, this, ctxt);
|
||||
impl_call_operator_delete (cd);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (unknown_side_effects)
|
||||
handle_unrecognized_call (call, ctxt);
|
||||
|
@ -2191,6 +2211,11 @@ region_model::maybe_update_for_edge (const superedge &edge,
|
|||
return apply_constraints_for_gswitch (*switch_sedge, switch_stmt, ctxt);
|
||||
}
|
||||
|
||||
/* Apply any constraints due to an exception being thrown. */
|
||||
if (const cfg_superedge *cfg_sedge = dyn_cast <const cfg_superedge *> (&edge))
|
||||
if (cfg_sedge->get_flags () & EDGE_EH)
|
||||
return apply_constraints_for_exception (last_stmt, ctxt);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -2349,6 +2374,34 @@ region_model::apply_constraints_for_gswitch (const switch_cfg_superedge &edge,
|
|||
}
|
||||
}
|
||||
|
||||
/* Apply any constraints due to an exception being thrown at LAST_STMT.
|
||||
|
||||
If they are feasible, add the constraints and return true.
|
||||
|
||||
Return false if the constraints contradict existing knowledge
|
||||
(and so the edge should not be taken). */
|
||||
|
||||
bool
|
||||
region_model::apply_constraints_for_exception (const gimple *last_stmt,
|
||||
region_model_context *ctxt)
|
||||
{
|
||||
gcc_assert (last_stmt);
|
||||
if (const gcall *call = dyn_cast <const gcall *> (last_stmt))
|
||||
if (tree callee_fndecl = get_fndecl_for_call (call, ctxt))
|
||||
if (is_named_call_p (callee_fndecl, "operator new", call, 1)
|
||||
|| is_named_call_p (callee_fndecl, "operator new []", call, 1))
|
||||
{
|
||||
/* We have an exception thrown from operator new.
|
||||
Add a constraint that the result was NULL, to avoid a false
|
||||
leak report due to the result being lost when following
|
||||
the EH edge. */
|
||||
if (tree lhs = gimple_call_lhs (call))
|
||||
return add_constraint (lhs, EQ_EXPR, null_pointer_node, ctxt);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* For use with push_frame when handling a top-level call within the analysis.
|
||||
PARAM has a defined but unknown initial value.
|
||||
Anything it points to has escaped, since the calling context "knows"
|
||||
|
|
|
@ -2556,6 +2556,8 @@ class region_model
|
|||
bool impl_call_malloc (const call_details &cd);
|
||||
bool impl_call_memset (const call_details &cd);
|
||||
bool impl_call_strlen (const call_details &cd);
|
||||
bool impl_call_operator_new (const call_details &cd);
|
||||
bool impl_call_operator_delete (const call_details &cd);
|
||||
|
||||
void handle_unrecognized_call (const gcall *call,
|
||||
region_model_context *ctxt);
|
||||
|
@ -2694,6 +2696,8 @@ class region_model
|
|||
bool apply_constraints_for_gswitch (const switch_cfg_superedge &edge,
|
||||
const gswitch *switch_stmt,
|
||||
region_model_context *ctxt);
|
||||
bool apply_constraints_for_exception (const gimple *last_stmt,
|
||||
region_model_context *ctxt);
|
||||
|
||||
int poison_any_pointers_to_descendents (const region *reg,
|
||||
enum poison_kind pkind);
|
||||
|
|
|
@ -48,6 +48,114 @@ namespace ana {
|
|||
|
||||
namespace {
|
||||
|
||||
class api;
|
||||
class malloc_state_machine;
|
||||
|
||||
/* An enum for discriminating between different kinds of allocation_state. */
|
||||
|
||||
enum resource_state
|
||||
{
|
||||
/* States that are independent of api. */
|
||||
|
||||
/* The start state. */
|
||||
RS_START,
|
||||
|
||||
/* State for a pointer that's known to be NULL. */
|
||||
RS_NULL,
|
||||
|
||||
/* State for a pointer that's known to not be on the heap (e.g. to a local
|
||||
or global). */
|
||||
RS_NON_HEAP,
|
||||
|
||||
/* Stop state, for pointers we don't want to track any more. */
|
||||
RS_STOP,
|
||||
|
||||
/* States that relate to a specific api. */
|
||||
|
||||
/* State for a pointer returned from the api's allocator that hasn't
|
||||
been checked for NULL.
|
||||
It could be a pointer to heap-allocated memory, or could be NULL. */
|
||||
RS_UNCHECKED,
|
||||
|
||||
/* State for a pointer returned from the api's allocator,
|
||||
known to be non-NULL. */
|
||||
RS_NONNULL,
|
||||
|
||||
/* State for a pointer passed to the api's deallocator. */
|
||||
RS_FREED
|
||||
};
|
||||
|
||||
/* Custom state subclass, which can optionally refer to an an api. */
|
||||
|
||||
struct allocation_state : public state_machine::state
|
||||
{
|
||||
allocation_state (const char *name, unsigned id,
|
||||
enum resource_state rs, const api *a)
|
||||
: state (name, id), m_rs (rs), m_api (a)
|
||||
{}
|
||||
|
||||
void dump_to_pp (pretty_printer *pp) const FINAL OVERRIDE;
|
||||
|
||||
const allocation_state *get_nonnull () const;
|
||||
|
||||
enum resource_state m_rs;
|
||||
const api *m_api;
|
||||
};
|
||||
|
||||
/* An enum for choosing which wording to use in various diagnostics
|
||||
when describing deallocations. */
|
||||
|
||||
enum wording
|
||||
{
|
||||
WORDING_FREED,
|
||||
WORDING_DELETED
|
||||
};
|
||||
|
||||
/* Represents a particular family of API calls for allocating/deallocating
|
||||
heap memory that should be matched e.g.
|
||||
- malloc/free
|
||||
- scalar new/delete
|
||||
- vector new[]/delete[]
|
||||
etc.
|
||||
|
||||
We track the expected deallocation function, but not the allocation
|
||||
function - there could be more than one allocator per deallocator. For
|
||||
example, there could be dozens of allocators for "free" beyond just
|
||||
malloc e.g. calloc, xstrdup, etc. We don't want to explode the number
|
||||
of states by tracking individual allocators in the exploded graph;
|
||||
we merely want to track "this value expects to have 'free' called on it".
|
||||
Perhaps we can reconstruct which allocator was used later, when emitting
|
||||
the path, if it's necessary for precision of wording of diagnostics. */
|
||||
|
||||
struct api
|
||||
{
|
||||
api (malloc_state_machine *sm,
|
||||
const char *name,
|
||||
const char *dealloc_funcname,
|
||||
enum wording wording);
|
||||
|
||||
/* An internal name for identifying this API in dumps. */
|
||||
const char *m_name;
|
||||
|
||||
/* The name of the deallocation function, for use in diagnostics. */
|
||||
const char *m_dealloc_funcname;
|
||||
|
||||
/* Which wording to use in diagnostics. */
|
||||
enum wording m_wording;
|
||||
|
||||
/* Pointers to api-specific states.
|
||||
These states are owned by the state_machine base class. */
|
||||
|
||||
/* State for an unchecked result from this api's allocator. */
|
||||
state_machine::state_t m_unchecked;
|
||||
|
||||
/* State for a known non-NULL result from this apis's allocator. */
|
||||
state_machine::state_t m_nonnull;
|
||||
|
||||
/* State for a value passed to this api's deallocator. */
|
||||
state_machine::state_t m_freed;
|
||||
};
|
||||
|
||||
/* A state machine for detecting misuses of the malloc/free API.
|
||||
|
||||
See sm-malloc.dot for an overview (keep this in-sync with that file). */
|
||||
|
@ -55,8 +163,13 @@ namespace {
|
|||
class malloc_state_machine : public state_machine
|
||||
{
|
||||
public:
|
||||
typedef allocation_state custom_data_t;
|
||||
|
||||
malloc_state_machine (logger *logger);
|
||||
|
||||
state_t
|
||||
add_state (const char *name, enum resource_state rs, const api *a);
|
||||
|
||||
bool inherited_state_p () const FINAL OVERRIDE { return false; }
|
||||
|
||||
state_machine::state_t
|
||||
|
@ -98,20 +211,15 @@ public:
|
|||
bool reset_when_passed_to_unknown_fn_p (state_t s,
|
||||
bool is_mutable) const FINAL OVERRIDE;
|
||||
|
||||
/* State for a pointer returned from malloc that hasn't been checked for
|
||||
NULL.
|
||||
It could be a pointer to heap-allocated memory, or could be NULL. */
|
||||
state_t m_unchecked;
|
||||
api m_malloc;
|
||||
api m_scalar_new;
|
||||
api m_vector_new;
|
||||
|
||||
/* States that are independent of api. */
|
||||
|
||||
/* State for a pointer that's known to be NULL. */
|
||||
state_t m_null;
|
||||
|
||||
/* State for a pointer to heap-allocated memory, known to be non-NULL. */
|
||||
state_t m_nonnull;
|
||||
|
||||
/* State for a pointer to freed memory. */
|
||||
state_t m_freed;
|
||||
|
||||
/* State for a pointer that's known to not be on the heap (e.g. to a local
|
||||
or global). */
|
||||
state_t m_non_heap; // TODO: or should this be a different state machine?
|
||||
|
@ -121,12 +229,89 @@ public:
|
|||
state_t m_stop;
|
||||
|
||||
private:
|
||||
void on_allocator_call (sm_context *sm_ctxt,
|
||||
const gcall *call,
|
||||
const api &ap) const;
|
||||
void on_deallocator_call (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gcall *call,
|
||||
const api &ap) const;
|
||||
void on_zero_assignment (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gimple *stmt,
|
||||
tree lhs) const;
|
||||
};
|
||||
|
||||
/* struct api. */
|
||||
|
||||
api::api (malloc_state_machine *sm,
|
||||
const char *name,
|
||||
const char *dealloc_funcname,
|
||||
enum wording wording)
|
||||
: m_name (name),
|
||||
m_dealloc_funcname (dealloc_funcname),
|
||||
m_wording (wording),
|
||||
m_unchecked (sm->add_state ("unchecked", RS_UNCHECKED, this)),
|
||||
m_nonnull (sm->add_state ("nonnull", RS_NONNULL, this)),
|
||||
m_freed (sm->add_state ("freed", RS_FREED, this))
|
||||
{
|
||||
}
|
||||
|
||||
/* Return STATE cast to the custom state subclass, or NULL for the start state.
|
||||
Everything should be an allocation_state apart from the start state. */
|
||||
|
||||
static const allocation_state *
|
||||
dyn_cast_allocation_state (state_machine::state_t state)
|
||||
{
|
||||
if (state->get_id () == 0)
|
||||
return NULL;
|
||||
return static_cast <const allocation_state *> (state);
|
||||
}
|
||||
|
||||
/* Return STATE cast to the custom state subclass, for a state that is
|
||||
already known to not be the start state . */
|
||||
|
||||
static const allocation_state *
|
||||
as_a_allocation_state (state_machine::state_t state)
|
||||
{
|
||||
gcc_assert (state->get_id () != 0);
|
||||
return static_cast <const allocation_state *> (state);
|
||||
}
|
||||
|
||||
/* Get the resource_state for STATE. */
|
||||
|
||||
static enum resource_state
|
||||
get_rs (state_machine::state_t state)
|
||||
{
|
||||
if (const allocation_state *astate = dyn_cast_allocation_state (state))
|
||||
return astate->m_rs;
|
||||
else
|
||||
return RS_START;
|
||||
}
|
||||
|
||||
/* Return true if STATE is an unchecked result from an allocator. */
|
||||
|
||||
static bool
|
||||
unchecked_p (state_machine::state_t state)
|
||||
{
|
||||
return get_rs (state) == RS_UNCHECKED;
|
||||
}
|
||||
|
||||
/* Return true if STATE is a non-null result from an allocator. */
|
||||
|
||||
static bool
|
||||
nonnull_p (state_machine::state_t state)
|
||||
{
|
||||
return get_rs (state) == RS_NONNULL;
|
||||
}
|
||||
|
||||
/* Return true if STATE is a value that has been passed to a deallocator. */
|
||||
|
||||
static bool
|
||||
freed_p (state_machine::state_t state)
|
||||
{
|
||||
return get_rs (state) == RS_FREED;
|
||||
}
|
||||
|
||||
/* Class for diagnostics relating to malloc_state_machine. */
|
||||
|
||||
class malloc_diagnostic : public pending_diagnostic
|
||||
|
@ -145,11 +330,11 @@ public:
|
|||
OVERRIDE
|
||||
{
|
||||
if (change.m_old_state == m_sm.get_start_state ()
|
||||
&& change.m_new_state == m_sm.m_unchecked)
|
||||
&& unchecked_p (change.m_new_state))
|
||||
// TODO: verify that it's the allocation stmt, not a copy
|
||||
return label_text::borrow ("allocated here");
|
||||
if (change.m_old_state == m_sm.m_unchecked
|
||||
&& change.m_new_state == m_sm.m_nonnull)
|
||||
if (unchecked_p (change.m_old_state)
|
||||
&& nonnull_p (change.m_new_state))
|
||||
{
|
||||
if (change.m_expr)
|
||||
return change.formatted_print ("assuming %qE is non-NULL",
|
||||
|
@ -160,7 +345,7 @@ public:
|
|||
}
|
||||
if (change.m_new_state == m_sm.m_null)
|
||||
{
|
||||
if (change.m_old_state == m_sm.m_unchecked)
|
||||
if (unchecked_p (change.m_old_state))
|
||||
{
|
||||
if (change.m_expr)
|
||||
return change.formatted_print ("assuming %qE is NULL",
|
||||
|
@ -188,13 +373,75 @@ protected:
|
|||
tree m_arg;
|
||||
};
|
||||
|
||||
/* Concrete subclass for reporting mismatching allocator/deallocator
|
||||
diagnostics. */
|
||||
|
||||
class mismatching_deallocation : public malloc_diagnostic
|
||||
{
|
||||
public:
|
||||
mismatching_deallocation (const malloc_state_machine &sm, tree arg,
|
||||
const api *expected_dealloc,
|
||||
const api *actual_dealloc)
|
||||
: malloc_diagnostic (sm, arg),
|
||||
m_expected_dealloc (expected_dealloc),
|
||||
m_actual_dealloc (actual_dealloc)
|
||||
{}
|
||||
|
||||
const char *get_kind () const FINAL OVERRIDE
|
||||
{
|
||||
return "mismatching_deallocation";
|
||||
}
|
||||
|
||||
bool emit (rich_location *rich_loc) FINAL OVERRIDE
|
||||
{
|
||||
auto_diagnostic_group d;
|
||||
diagnostic_metadata m;
|
||||
m.add_cwe (762); /* CWE-762: Mismatched Memory Management Routines. */
|
||||
return warning_meta (rich_loc, m, OPT_Wanalyzer_mismatching_deallocation,
|
||||
"%qE should have been deallocated with %qs"
|
||||
" but was deallocated with %qs",
|
||||
m_arg, m_expected_dealloc->m_dealloc_funcname,
|
||||
m_actual_dealloc->m_dealloc_funcname);
|
||||
}
|
||||
|
||||
label_text describe_state_change (const evdesc::state_change &change)
|
||||
FINAL OVERRIDE
|
||||
{
|
||||
if (unchecked_p (change.m_new_state))
|
||||
{
|
||||
m_alloc_event = change.m_event_id;
|
||||
return change.formatted_print ("allocated here"
|
||||
" (expects deallocation with %qs)",
|
||||
m_expected_dealloc->m_dealloc_funcname);
|
||||
}
|
||||
return malloc_diagnostic::describe_state_change (change);
|
||||
}
|
||||
|
||||
label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
|
||||
{
|
||||
if (m_alloc_event.known_p ())
|
||||
return ev.formatted_print
|
||||
("deallocated with %qs here;"
|
||||
" allocation at %@ expects deallocation with %qs",
|
||||
m_actual_dealloc->m_dealloc_funcname, &m_alloc_event,
|
||||
m_expected_dealloc->m_dealloc_funcname);
|
||||
return ev.formatted_print ("deallocated with %qs here",
|
||||
m_actual_dealloc->m_dealloc_funcname);
|
||||
}
|
||||
|
||||
private:
|
||||
diagnostic_event_id_t m_alloc_event;
|
||||
const api *m_expected_dealloc;
|
||||
const api *m_actual_dealloc;
|
||||
};
|
||||
|
||||
/* Concrete subclass for reporting double-free diagnostics. */
|
||||
|
||||
class double_free : public malloc_diagnostic
|
||||
{
|
||||
public:
|
||||
double_free (const malloc_state_machine &sm, tree arg)
|
||||
: malloc_diagnostic (sm, arg)
|
||||
double_free (const malloc_state_machine &sm, tree arg, const char *funcname)
|
||||
: malloc_diagnostic (sm, arg), m_funcname (funcname)
|
||||
{}
|
||||
|
||||
const char *get_kind () const FINAL OVERRIDE { return "double_free"; }
|
||||
|
@ -205,16 +452,16 @@ public:
|
|||
diagnostic_metadata m;
|
||||
m.add_cwe (415); /* CWE-415: Double Free. */
|
||||
return warning_meta (rich_loc, m, OPT_Wanalyzer_double_free,
|
||||
"double-%<free%> of %qE", m_arg);
|
||||
"double-%<%s%> of %qE", m_funcname, m_arg);
|
||||
}
|
||||
|
||||
label_text describe_state_change (const evdesc::state_change &change)
|
||||
FINAL OVERRIDE
|
||||
{
|
||||
if (change.m_new_state == m_sm.m_freed)
|
||||
if (freed_p (change.m_new_state))
|
||||
{
|
||||
m_first_free_event = change.m_event_id;
|
||||
return change.formatted_print ("first %qs here", "free");
|
||||
return change.formatted_print ("first %qs here", m_funcname);
|
||||
}
|
||||
return malloc_diagnostic::describe_state_change (change);
|
||||
}
|
||||
|
@ -222,7 +469,7 @@ public:
|
|||
label_text describe_call_with_state (const evdesc::call_with_state &info)
|
||||
FINAL OVERRIDE
|
||||
{
|
||||
if (info.m_state == m_sm.m_freed)
|
||||
if (freed_p (info.m_state))
|
||||
return info.formatted_print
|
||||
("passing freed pointer %qE in call to %qE from %qE",
|
||||
info.m_expr, info.m_callee_fndecl, info.m_caller_fndecl);
|
||||
|
@ -233,13 +480,14 @@ public:
|
|||
{
|
||||
if (m_first_free_event.known_p ())
|
||||
return ev.formatted_print ("second %qs here; first %qs was at %@",
|
||||
"free", "free",
|
||||
m_funcname, m_funcname,
|
||||
&m_first_free_event);
|
||||
return ev.formatted_print ("second %qs here", "free");
|
||||
return ev.formatted_print ("second %qs here", m_funcname);
|
||||
}
|
||||
|
||||
private:
|
||||
diagnostic_event_id_t m_first_free_event;
|
||||
const char *m_funcname;
|
||||
};
|
||||
|
||||
/* Abstract subclass for describing possible bad uses of NULL.
|
||||
|
@ -256,7 +504,7 @@ public:
|
|||
FINAL OVERRIDE
|
||||
{
|
||||
if (change.m_old_state == m_sm.get_start_state ()
|
||||
&& change.m_new_state == m_sm.m_unchecked)
|
||||
&& unchecked_p (change.m_new_state))
|
||||
{
|
||||
m_origin_of_unchecked_event = change.m_event_id;
|
||||
return label_text::borrow ("this call could return NULL");
|
||||
|
@ -267,7 +515,7 @@ public:
|
|||
label_text describe_return_of_state (const evdesc::return_of_state &info)
|
||||
FINAL OVERRIDE
|
||||
{
|
||||
if (info.m_state == m_sm.m_unchecked)
|
||||
if (unchecked_p (info.m_state))
|
||||
return info.formatted_print ("possible return of NULL to %qE from %qE",
|
||||
info.m_caller_fndecl, info.m_callee_fndecl);
|
||||
return label_text ();
|
||||
|
@ -480,8 +728,9 @@ private:
|
|||
class use_after_free : public malloc_diagnostic
|
||||
{
|
||||
public:
|
||||
use_after_free (const malloc_state_machine &sm, tree arg)
|
||||
: malloc_diagnostic (sm, arg)
|
||||
use_after_free (const malloc_state_machine &sm, tree arg,
|
||||
const api *a)
|
||||
: malloc_diagnostic (sm, arg), m_api (a)
|
||||
{}
|
||||
|
||||
const char *get_kind () const FINAL OVERRIDE { return "use_after_free"; }
|
||||
|
@ -492,31 +741,52 @@ public:
|
|||
diagnostic_metadata m;
|
||||
m.add_cwe (416);
|
||||
return warning_meta (rich_loc, m, OPT_Wanalyzer_use_after_free,
|
||||
"use after %<free%> of %qE", m_arg);
|
||||
"use after %<%s%> of %qE",
|
||||
m_api->m_dealloc_funcname, m_arg);
|
||||
}
|
||||
|
||||
label_text describe_state_change (const evdesc::state_change &change)
|
||||
FINAL OVERRIDE
|
||||
{
|
||||
if (change.m_new_state == m_sm.m_freed)
|
||||
if (freed_p (change.m_new_state))
|
||||
{
|
||||
m_free_event = change.m_event_id;
|
||||
return label_text::borrow ("freed here");
|
||||
switch (m_api->m_wording)
|
||||
{
|
||||
default:
|
||||
gcc_unreachable ();
|
||||
case WORDING_FREED:
|
||||
return label_text::borrow ("freed here");
|
||||
case WORDING_DELETED:
|
||||
return label_text::borrow ("deleted here");
|
||||
}
|
||||
}
|
||||
return malloc_diagnostic::describe_state_change (change);
|
||||
}
|
||||
|
||||
label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
|
||||
{
|
||||
const char *funcname = m_api->m_dealloc_funcname;
|
||||
if (m_free_event.known_p ())
|
||||
return ev.formatted_print ("use after %<free%> of %qE; freed at %@",
|
||||
ev.m_expr, &m_free_event);
|
||||
switch (m_api->m_wording)
|
||||
{
|
||||
default:
|
||||
gcc_unreachable ();
|
||||
case WORDING_FREED:
|
||||
return ev.formatted_print ("use after %<%s%> of %qE; freed at %@",
|
||||
funcname, ev.m_expr, &m_free_event);
|
||||
case WORDING_DELETED:
|
||||
return ev.formatted_print ("use after %<%s%> of %qE; deleted at %@",
|
||||
funcname, ev.m_expr, &m_free_event);
|
||||
}
|
||||
else
|
||||
return ev.formatted_print ("use after %<free%> of %qE", ev.m_expr);
|
||||
return ev.formatted_print ("use after %<%s%> of %qE",
|
||||
funcname, ev.m_expr);
|
||||
}
|
||||
|
||||
private:
|
||||
diagnostic_event_id_t m_free_event;
|
||||
const api *m_api;
|
||||
};
|
||||
|
||||
class malloc_leak : public malloc_diagnostic
|
||||
|
@ -542,9 +812,9 @@ public:
|
|||
label_text describe_state_change (const evdesc::state_change &change)
|
||||
FINAL OVERRIDE
|
||||
{
|
||||
if (change.m_new_state == m_sm.m_unchecked)
|
||||
if (unchecked_p (change.m_new_state))
|
||||
{
|
||||
m_malloc_event = change.m_event_id;
|
||||
m_alloc_event = change.m_event_id;
|
||||
return label_text::borrow ("allocated here");
|
||||
}
|
||||
return malloc_diagnostic::describe_state_change (change);
|
||||
|
@ -554,31 +824,32 @@ public:
|
|||
{
|
||||
if (ev.m_expr)
|
||||
{
|
||||
if (m_malloc_event.known_p ())
|
||||
if (m_alloc_event.known_p ())
|
||||
return ev.formatted_print ("%qE leaks here; was allocated at %@",
|
||||
ev.m_expr, &m_malloc_event);
|
||||
ev.m_expr, &m_alloc_event);
|
||||
else
|
||||
return ev.formatted_print ("%qE leaks here", ev.m_expr);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_malloc_event.known_p ())
|
||||
if (m_alloc_event.known_p ())
|
||||
return ev.formatted_print ("%qs leaks here; was allocated at %@",
|
||||
"<unknown>", &m_malloc_event);
|
||||
"<unknown>", &m_alloc_event);
|
||||
else
|
||||
return ev.formatted_print ("%qs leaks here", "<unknown>");
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
diagnostic_event_id_t m_malloc_event;
|
||||
diagnostic_event_id_t m_alloc_event;
|
||||
};
|
||||
|
||||
class free_of_non_heap : public malloc_diagnostic
|
||||
{
|
||||
public:
|
||||
free_of_non_heap (const malloc_state_machine &sm, tree arg)
|
||||
: malloc_diagnostic (sm, arg), m_kind (KIND_UNKNOWN)
|
||||
free_of_non_heap (const malloc_state_machine &sm, tree arg,
|
||||
const char *funcname)
|
||||
: malloc_diagnostic (sm, arg), m_funcname (funcname), m_kind (KIND_UNKNOWN)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -602,15 +873,15 @@ public:
|
|||
gcc_unreachable ();
|
||||
case KIND_UNKNOWN:
|
||||
return warning_meta (rich_loc, m, OPT_Wanalyzer_free_of_non_heap,
|
||||
"%<free%> of %qE which points to memory"
|
||||
"%<%s%> of %qE which points to memory"
|
||||
" not on the heap",
|
||||
m_arg);
|
||||
m_funcname, m_arg);
|
||||
break;
|
||||
case KIND_ALLOCA:
|
||||
return warning_meta (rich_loc, m, OPT_Wanalyzer_free_of_non_heap,
|
||||
"%<free%> of memory allocated on the stack by"
|
||||
"%<%s%> of memory allocated on the stack by"
|
||||
" %qs (%qE) will corrupt the heap",
|
||||
"alloca", m_arg);
|
||||
m_funcname, "alloca", m_arg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -639,7 +910,7 @@ public:
|
|||
|
||||
label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
|
||||
{
|
||||
return ev.formatted_print ("call to %qs here", "free");
|
||||
return ev.formatted_print ("call to %qs here", m_funcname);
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -648,20 +919,54 @@ private:
|
|||
KIND_UNKNOWN,
|
||||
KIND_ALLOCA
|
||||
};
|
||||
const char *m_funcname;
|
||||
enum kind m_kind;
|
||||
};
|
||||
|
||||
/* struct allocation_state : public state_machine::state. */
|
||||
|
||||
/* Implementation of state_machine::state::dump_to_pp vfunc
|
||||
for allocation_state: append the API that this allocation is
|
||||
associated with. */
|
||||
|
||||
void
|
||||
allocation_state::dump_to_pp (pretty_printer *pp) const
|
||||
{
|
||||
state_machine::state::dump_to_pp (pp);
|
||||
if (m_api)
|
||||
pp_printf (pp, " (%s)", m_api->m_name);
|
||||
}
|
||||
|
||||
/* Given a allocation_state for an api, get the "nonnull" state
|
||||
for the corresponding allocator. */
|
||||
|
||||
const allocation_state *
|
||||
allocation_state::get_nonnull () const
|
||||
{
|
||||
gcc_assert (m_api);
|
||||
return as_a_allocation_state (m_api->m_nonnull);
|
||||
}
|
||||
|
||||
/* malloc_state_machine's ctor. */
|
||||
|
||||
malloc_state_machine::malloc_state_machine (logger *logger)
|
||||
: state_machine ("malloc", logger)
|
||||
: state_machine ("malloc", logger),
|
||||
m_malloc (this, "malloc", "free", WORDING_FREED),
|
||||
m_scalar_new (this, "new", "delete", WORDING_DELETED),
|
||||
m_vector_new (this, "new[]", "delete[]", WORDING_DELETED)
|
||||
{
|
||||
m_unchecked = add_state ("unchecked");
|
||||
m_null = add_state ("null");
|
||||
m_nonnull = add_state ("nonnull");
|
||||
m_freed = add_state ("freed");
|
||||
m_non_heap = add_state ("non-heap");
|
||||
m_stop = add_state ("stop");
|
||||
gcc_assert (m_start->get_id () == 0);
|
||||
m_null = add_state ("null", RS_FREED, NULL);
|
||||
m_non_heap = add_state ("non-heap", RS_NON_HEAP, NULL);
|
||||
m_stop = add_state ("stop", RS_STOP, NULL);
|
||||
}
|
||||
|
||||
state_machine::state_t
|
||||
malloc_state_machine::add_state (const char *name, enum resource_state rs,
|
||||
const api *a)
|
||||
{
|
||||
return add_custom_state (new allocation_state (name, alloc_state_id (),
|
||||
rs, a));
|
||||
}
|
||||
|
||||
/* Implementation of state_machine::on_stmt vfunc for malloc_state_machine. */
|
||||
|
@ -681,13 +986,23 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
|
|||
|| is_named_call_p (callee_fndecl, "__builtin_malloc", call, 1)
|
||||
|| is_named_call_p (callee_fndecl, "__builtin_calloc", call, 2))
|
||||
{
|
||||
tree lhs = gimple_call_lhs (call);
|
||||
if (lhs)
|
||||
sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
|
||||
else
|
||||
{
|
||||
/* TODO: report leak. */
|
||||
}
|
||||
on_allocator_call (sm_ctxt, call, m_malloc);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_named_call_p (callee_fndecl, "operator new", call, 1))
|
||||
on_allocator_call (sm_ctxt, call, m_scalar_new);
|
||||
else if (is_named_call_p (callee_fndecl, "operator new []", call, 1))
|
||||
on_allocator_call (sm_ctxt, call, m_vector_new);
|
||||
else if (is_named_call_p (callee_fndecl, "operator delete", call, 1)
|
||||
|| is_named_call_p (callee_fndecl, "operator delete", call, 2))
|
||||
{
|
||||
on_deallocator_call (sm_ctxt, node, call, m_scalar_new);
|
||||
return true;
|
||||
}
|
||||
else if (is_named_call_p (callee_fndecl, "operator delete []", call, 1))
|
||||
{
|
||||
on_deallocator_call (sm_ctxt, node, call, m_vector_new);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -704,32 +1019,7 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
|
|||
|| is_std_named_call_p (callee_fndecl, "free", call, 1)
|
||||
|| is_named_call_p (callee_fndecl, "__builtin_free", call, 1))
|
||||
{
|
||||
tree arg = gimple_call_arg (call, 0);
|
||||
tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
|
||||
|
||||
/* start/unchecked/nonnull -> freed. */
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_start, m_freed);
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_freed);
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_nonnull, m_freed);
|
||||
|
||||
/* Keep state "null" as-is, rather than transitioning to "free";
|
||||
we don't want to complain about double-free of NULL. */
|
||||
|
||||
/* freed -> stop, with warning. */
|
||||
if (sm_ctxt->get_state (stmt, arg) == m_freed)
|
||||
{
|
||||
sm_ctxt->warn (node, stmt, arg,
|
||||
new double_free (*this, diag_arg));
|
||||
sm_ctxt->set_next_state (stmt, arg, m_stop);
|
||||
}
|
||||
|
||||
/* non-heap -> stop, with warning. */
|
||||
if (sm_ctxt->get_state (stmt, arg) == m_non_heap)
|
||||
{
|
||||
sm_ctxt->warn (node, stmt, arg,
|
||||
new free_of_non_heap (*this, diag_arg));
|
||||
sm_ctxt->set_next_state (stmt, arg, m_stop);
|
||||
}
|
||||
on_deallocator_call (sm_ctxt, node, call, m_malloc);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -752,13 +1042,16 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
|
|||
tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
|
||||
state_t state = sm_ctxt->get_state (stmt, arg);
|
||||
/* Can't use a switch as the states are non-const. */
|
||||
if (state == m_unchecked)
|
||||
if (unchecked_p (state))
|
||||
{
|
||||
sm_ctxt->warn (node, stmt, arg,
|
||||
new possible_null_arg (*this, diag_arg,
|
||||
callee_fndecl,
|
||||
i));
|
||||
sm_ctxt->set_next_state (stmt, arg, m_nonnull);
|
||||
const allocation_state *astate
|
||||
= as_a_allocation_state (state);
|
||||
sm_ctxt->set_next_state (stmt, arg,
|
||||
astate->get_nonnull ());
|
||||
}
|
||||
else if (state == m_null)
|
||||
{
|
||||
|
@ -776,7 +1069,7 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
|
|||
|
||||
if (tree lhs = sm_ctxt->is_zero_assignment (stmt))
|
||||
if (any_pointer_p (lhs))
|
||||
on_zero_assignment (sm_ctxt, node, stmt,lhs);
|
||||
on_zero_assignment (sm_ctxt, stmt,lhs);
|
||||
|
||||
/* If we have "LHS = &EXPR;" and EXPR is something other than a MEM_REF,
|
||||
transition LHS from start to non_heap.
|
||||
|
@ -813,12 +1106,12 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
|
|||
tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
|
||||
|
||||
state_t state = sm_ctxt->get_state (stmt, arg);
|
||||
/* Can't use a switch as the states are non-const. */
|
||||
if (state == m_unchecked)
|
||||
if (unchecked_p (state))
|
||||
{
|
||||
sm_ctxt->warn (node, stmt, arg,
|
||||
new possible_null_deref (*this, diag_arg));
|
||||
sm_ctxt->set_next_state (stmt, arg, m_nonnull);
|
||||
const allocation_state *astate = as_a_allocation_state (state);
|
||||
sm_ctxt->set_next_state (stmt, arg, astate->get_nonnull ());
|
||||
}
|
||||
else if (state == m_null)
|
||||
{
|
||||
|
@ -826,10 +1119,12 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
|
|||
new null_deref (*this, diag_arg));
|
||||
sm_ctxt->set_next_state (stmt, arg, m_stop);
|
||||
}
|
||||
else if (state == m_freed)
|
||||
else if (freed_p (state))
|
||||
{
|
||||
const allocation_state *astate = as_a_allocation_state (state);
|
||||
sm_ctxt->warn (node, stmt, arg,
|
||||
new use_after_free (*this, diag_arg));
|
||||
new use_after_free (*this, diag_arg,
|
||||
astate->m_api));
|
||||
sm_ctxt->set_next_state (stmt, arg, m_stop);
|
||||
}
|
||||
}
|
||||
|
@ -837,18 +1132,87 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
|
|||
return false;
|
||||
}
|
||||
|
||||
/* Handle a call to an allocator. */
|
||||
|
||||
void
|
||||
malloc_state_machine::on_allocator_call (sm_context *sm_ctxt,
|
||||
const gcall *call,
|
||||
const api &ap) const
|
||||
{
|
||||
tree lhs = gimple_call_lhs (call);
|
||||
if (lhs)
|
||||
{
|
||||
if (sm_ctxt->get_state (call, lhs) == m_start)
|
||||
sm_ctxt->set_next_state (call, lhs, ap.m_unchecked);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* TODO: report leak. */
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
malloc_state_machine::on_deallocator_call (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gcall *call,
|
||||
const api &ap) const
|
||||
{
|
||||
tree arg = gimple_call_arg (call, 0);
|
||||
tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
|
||||
|
||||
state_t state = sm_ctxt->get_state (call, arg);
|
||||
|
||||
/* start/unchecked/nonnull -> freed. */
|
||||
if (state == m_start)
|
||||
sm_ctxt->set_next_state (call, arg, ap.m_freed);
|
||||
else if (unchecked_p (state) || nonnull_p (state))
|
||||
{
|
||||
const allocation_state *astate = as_a_allocation_state (state);
|
||||
|
||||
if (astate->m_api != &ap)
|
||||
{
|
||||
/* Wrong allocator. */
|
||||
pending_diagnostic *d
|
||||
= new mismatching_deallocation (*this, diag_arg,
|
||||
astate->m_api, &ap);
|
||||
sm_ctxt->warn (node, call, arg, d);
|
||||
}
|
||||
sm_ctxt->set_next_state (call, arg, ap.m_freed);
|
||||
}
|
||||
|
||||
/* Keep state "null" as-is, rather than transitioning to "freed";
|
||||
we don't want to complain about double-free of NULL. */
|
||||
|
||||
else if (state == ap.m_freed)
|
||||
{
|
||||
/* freed -> stop, with warning. */
|
||||
sm_ctxt->warn (node, call, arg,
|
||||
new double_free (*this, diag_arg,
|
||||
ap.m_dealloc_funcname));
|
||||
sm_ctxt->set_next_state (call, arg, m_stop);
|
||||
}
|
||||
else if (state == m_non_heap)
|
||||
{
|
||||
/* non-heap -> stop, with warning. */
|
||||
sm_ctxt->warn (node, call, arg,
|
||||
new free_of_non_heap (*this, diag_arg,
|
||||
ap.m_dealloc_funcname));
|
||||
sm_ctxt->set_next_state (call, arg, m_stop);
|
||||
}
|
||||
}
|
||||
|
||||
/* Implementation of state_machine::on_phi vfunc for malloc_state_machine. */
|
||||
|
||||
void
|
||||
malloc_state_machine::on_phi (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const supernode *node ATTRIBUTE_UNUSED,
|
||||
const gphi *phi,
|
||||
tree rhs) const
|
||||
{
|
||||
if (zerop (rhs))
|
||||
{
|
||||
tree lhs = gimple_phi_result (phi);
|
||||
on_zero_assignment (sm_ctxt, node, phi, lhs);
|
||||
on_zero_assignment (sm_ctxt, phi, lhs);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -857,7 +1221,7 @@ malloc_state_machine::on_phi (sm_context *sm_ctxt,
|
|||
|
||||
void
|
||||
malloc_state_machine::on_condition (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const supernode *node ATTRIBUTE_UNUSED,
|
||||
const gimple *stmt,
|
||||
tree lhs,
|
||||
enum tree_code op,
|
||||
|
@ -874,14 +1238,19 @@ malloc_state_machine::on_condition (sm_context *sm_ctxt,
|
|||
if (op == NE_EXPR)
|
||||
{
|
||||
log ("got 'ARG != 0' match");
|
||||
sm_ctxt->on_transition (node, stmt,
|
||||
lhs, m_unchecked, m_nonnull);
|
||||
state_t s = sm_ctxt->get_state (stmt, lhs);
|
||||
if (unchecked_p (s))
|
||||
{
|
||||
const allocation_state *astate = as_a_allocation_state (s);
|
||||
sm_ctxt->set_next_state (stmt, lhs, astate->get_nonnull ());
|
||||
}
|
||||
}
|
||||
else if (op == EQ_EXPR)
|
||||
{
|
||||
log ("got 'ARG == 0' match");
|
||||
sm_ctxt->on_transition (node, stmt,
|
||||
lhs, m_unchecked, m_null);
|
||||
state_t s = sm_ctxt->get_state (stmt, lhs);
|
||||
if (unchecked_p (s))
|
||||
sm_ctxt->set_next_state (stmt, lhs, m_null);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -892,7 +1261,8 @@ malloc_state_machine::on_condition (sm_context *sm_ctxt,
|
|||
bool
|
||||
malloc_state_machine::can_purge_p (state_t s) const
|
||||
{
|
||||
return s != m_unchecked && s != m_nonnull;
|
||||
enum resource_state rs = get_rs (s);
|
||||
return rs != RS_UNCHECKED && rs != RS_NONNULL;
|
||||
}
|
||||
|
||||
/* Implementation of state_machine::on_leak vfunc for malloc_state_machine
|
||||
|
@ -926,14 +1296,16 @@ malloc_state_machine::reset_when_passed_to_unknown_fn_p (state_t s,
|
|||
|
||||
void
|
||||
malloc_state_machine::on_zero_assignment (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gimple *stmt,
|
||||
tree lhs) const
|
||||
{
|
||||
sm_ctxt->on_transition (node, stmt, lhs, m_start, m_null);
|
||||
sm_ctxt->on_transition (node, stmt, lhs, m_unchecked, m_null);
|
||||
sm_ctxt->on_transition (node, stmt, lhs, m_nonnull, m_null);
|
||||
sm_ctxt->on_transition (node, stmt, lhs, m_freed, m_null);
|
||||
state_t s = sm_ctxt->get_state (stmt, lhs);
|
||||
enum resource_state rs = get_rs (s);
|
||||
if (rs == RS_START
|
||||
|| rs == RS_UNCHECKED
|
||||
|| rs == RS_NONNULL
|
||||
|| rs == RS_FREED)
|
||||
sm_ctxt->set_next_state (stmt, lhs, m_null);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
|
|
@ -125,6 +125,12 @@ public:
|
|||
|
||||
protected:
|
||||
state_t add_state (const char *name);
|
||||
state_t add_custom_state (state *s)
|
||||
{
|
||||
m_states.safe_push (s);
|
||||
return s;
|
||||
}
|
||||
|
||||
unsigned alloc_state_id () { return m_next_state_id++; }
|
||||
|
||||
private:
|
||||
|
|
|
@ -414,6 +414,7 @@ Objective-C and Objective-C++ Dialects}.
|
|||
-Wno-analyzer-file-leak @gol
|
||||
-Wno-analyzer-free-of-non-heap @gol
|
||||
-Wno-analyzer-malloc-leak @gol
|
||||
-Wno-analyzer-mismatching-deallocation @gol
|
||||
-Wno-analyzer-null-argument @gol
|
||||
-Wno-analyzer-null-dereference @gol
|
||||
-Wno-analyzer-possible-null-argument @gol
|
||||
|
@ -8645,6 +8646,7 @@ Enabling this option effectively enables the following warnings:
|
|||
-Wanalyzer-file-leak @gol
|
||||
-Wanalyzer-free-of-non-heap @gol
|
||||
-Wanalyzer-malloc-leak @gol
|
||||
-Wanalyzer-mismatching-deallocation @gol
|
||||
-Wanalyzer-possible-null-argument @gol
|
||||
-Wanalyzer-possible-null-dereference @gol
|
||||
-Wanalyzer-null-argument @gol
|
||||
|
@ -8729,6 +8731,17 @@ to disable it.
|
|||
This diagnostic warns for paths through the code in which a
|
||||
pointer allocated via @code{malloc} is leaked.
|
||||
|
||||
@item -Wno-analyzer-mismatching-deallocation
|
||||
@opindex Wanalyzer-mismatching-deallocation
|
||||
@opindex Wno-analyzer-mismatching-deallocation
|
||||
This warning requires @option{-fanalyzer}, which enables it; use
|
||||
@option{-Wno-analyzer-mismatching-deallocation}
|
||||
to disable it.
|
||||
|
||||
This diagnostic warns for paths through the code in which the
|
||||
wrong deallocation function is called on a pointer value, based on
|
||||
which function was used to allocate the pointer value.
|
||||
|
||||
@item -Wno-analyzer-possible-null-argument
|
||||
@opindex Wanalyzer-possible-null-argument
|
||||
@opindex Wno-analyzer-possible-null-argument
|
||||
|
|
52
gcc/testsuite/g++.dg/analyzer/new-1.C
Normal file
52
gcc/testsuite/g++.dg/analyzer/new-1.C
Normal file
|
@ -0,0 +1,52 @@
|
|||
void test_1 ()
|
||||
{
|
||||
char* p = new char; // { dg-message "allocated here" }
|
||||
} // { dg-warning "leak of 'p'" }
|
||||
|
||||
void test_2 ()
|
||||
{
|
||||
char* p = new char;
|
||||
delete p;
|
||||
}
|
||||
|
||||
/* double-delete shows up as use-after-delete
|
||||
due to a clobber before the delete. */
|
||||
|
||||
void test_3 ()
|
||||
{
|
||||
char* p = new char;
|
||||
delete p; // { dg-message "deleted here" }
|
||||
delete p;
|
||||
} // { dg-warning "use after 'delete'" }
|
||||
// FIXME: should be on the 2nd delete, not here
|
||||
|
||||
void test_4 ()
|
||||
{
|
||||
char *p = new char[16]; // { dg-message "allocated here" }
|
||||
delete[] p; // { dg-message "first 'delete\\\[\\\]' here" }
|
||||
delete[] p; // { dg-warning "double-'delete\\\[\\\]' of 'p'" }
|
||||
}
|
||||
|
||||
void test_5 ()
|
||||
{
|
||||
char *p = new char[16];
|
||||
delete p; // { dg-warning "'p' should have been deallocated with 'delete\\\[\\\]' but was deallocated with 'delete'" }
|
||||
}
|
||||
|
||||
void test_6 ()
|
||||
{
|
||||
char *p = new char;
|
||||
delete[] p; // { dg-warning "'p' should have been deallocated with 'delete' but was deallocated with 'delete\\\[\\\]'" }
|
||||
}
|
||||
|
||||
char test_7 (char *p)
|
||||
{
|
||||
delete p; // { dg-message "deleted here" }
|
||||
return *p; // { dg-warning "use after 'delete' of 'p'" }
|
||||
}
|
||||
|
||||
char test_8 (char *p)
|
||||
{
|
||||
delete[] p; // { dg-message "deleted here" }
|
||||
return *p; // { dg-warning "use after 'delete\\\[\\\]' of 'p'" }
|
||||
}
|
21
gcc/testsuite/g++.dg/analyzer/new-vs-malloc.C
Normal file
21
gcc/testsuite/g++.dg/analyzer/new-vs-malloc.C
Normal file
|
@ -0,0 +1,21 @@
|
|||
#include <cstdlib>
|
||||
|
||||
struct s {};
|
||||
|
||||
void test_1 ()
|
||||
{
|
||||
s *p = new s; // { dg-message "allocated here \\(expects deallocation with 'delete'\\)"
|
||||
free (p); // { dg-warning "'p' should have been deallocated with 'delete' but was deallocated with 'free'" }
|
||||
}
|
||||
|
||||
void test_2 ()
|
||||
{
|
||||
char *p = new char[16]; // { dg-message "allocated here \\(expects deallocation with 'delete\\\[\\\]'\\)"
|
||||
free (p); // { dg-warning "'p' should have been deallocated with 'delete\\\[\\\]' but was deallocated with 'free'" }
|
||||
}
|
||||
|
||||
void test_3 ()
|
||||
{
|
||||
char *p = (char *)malloc (16); // { dg-message "allocated here \\(expects deallocation with 'free'\\)"
|
||||
delete[] p; // { dg-warning "'p' should have been deallocated with 'free' but was deallocated with 'delete\\\[\\\]'" }
|
||||
}
|
Loading…
Add table
Reference in a new issue