aboutsummaryrefslogtreecommitdiff
path: root/gcc
diff options
context:
space:
mode:
Diffstat (limited to 'gcc')
-rw-r--r--gcc/analyzer/analyzer.h1
-rw-r--r--gcc/analyzer/diagnostic-manager.cc15
-rw-r--r--gcc/analyzer/region-model-impl-calls.cc9
-rw-r--r--gcc/analyzer/region-model.cc9
-rw-r--r--gcc/analyzer/region-model.h1
-rw-r--r--gcc/analyzer/sm-malloc.cc767
-rw-r--r--gcc/attribs.h2
-rw-r--r--gcc/builtins.c10
-rw-r--r--gcc/doc/extend.texi52
-rw-r--r--gcc/doc/invoke.texi14
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/attr-malloc-1.c75
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/attr-malloc-2.c24
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/attr-malloc-4.c21
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/attr-malloc-5.c12
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/attr-malloc-6.c228
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/attr-malloc-CVE-2019-19078-usb-leak.c224
-rw-r--r--gcc/testsuite/gcc.dg/analyzer/attr-malloc-misuses.c18
17 files changed, 1354 insertions, 128 deletions
diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h
index 6996092..f50ac66 100644
--- a/gcc/analyzer/analyzer.h
+++ b/gcc/analyzer/analyzer.h
@@ -205,6 +205,7 @@ extern bool is_special_named_call_p (const gcall *call, const char *funcname,
extern bool is_named_call_p (tree fndecl, const char *funcname);
extern bool is_named_call_p (tree fndecl, const char *funcname,
const gcall *call, unsigned int num_args);
+extern bool is_std_named_call_p (tree fndecl, const char *funcname);
extern bool is_std_named_call_p (tree fndecl, const char *funcname,
const gcall *call, unsigned int num_args);
extern bool is_setjmp_call_p (const gcall *call);
diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc
index 3625e3f..22ca024 100644
--- a/gcc/analyzer/diagnostic-manager.cc
+++ b/gcc/analyzer/diagnostic-manager.cc
@@ -189,6 +189,8 @@ public:
return m_feasibility_problem;
}
+ const state_machine *get_sm () const { return m_sd.m_sm; }
+
private:
typedef reachability<eg_traits> enode_reachability;
@@ -709,9 +711,11 @@ diagnostic_manager::build_emission_path (const path_builder &pb,
class state_change_event_creator : public state_change_visitor
{
public:
- state_change_event_creator (const exploded_edge &eedge,
+ state_change_event_creator (const path_builder &pb,
+ const exploded_edge &eedge,
checker_path *emission_path)
- : m_eedge (eedge),
+ : m_pb (pb),
+ m_eedge (eedge),
m_emission_path (emission_path)
{}
@@ -720,6 +724,8 @@ public:
state_machine::state_t dst_sm_val)
FINAL OVERRIDE
{
+ if (&sm != m_pb.get_sm ())
+ return false;
const exploded_node *src_node = m_eedge.m_src;
const program_point &src_point = src_node->get_point ();
const int src_stack_depth = src_point.get_stack_depth ();
@@ -748,6 +754,8 @@ public:
const svalue *sval,
const svalue *dst_origin_sval) FINAL OVERRIDE
{
+ if (&sm != m_pb.get_sm ())
+ return false;
const exploded_node *src_node = m_eedge.m_src;
const program_point &src_point = src_node->get_point ();
const int src_stack_depth = src_point.get_stack_depth ();
@@ -783,6 +791,7 @@ public:
return false;
}
+ const path_builder &m_pb;
const exploded_edge &m_eedge;
checker_path *m_emission_path;
};
@@ -1002,7 +1011,7 @@ diagnostic_manager::add_events_for_eedge (const path_builder &pb,
| ~~~~~~~~~~~~~^~~~~
| |
| (3) ...to here (end_cfg_edge_event). */
- state_change_event_creator visitor (eedge, emission_path);
+ state_change_event_creator visitor (pb, eedge, emission_path);
for_each_state_change (src_state, dst_state, pb.get_ext_state (),
&visitor);
diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc
index 1b81c74..d3b66ba 100644
--- a/gcc/analyzer/region-model-impl-calls.cc
+++ b/gcc/analyzer/region-model-impl-calls.cc
@@ -436,6 +436,15 @@ region_model::impl_call_strlen (const call_details &cd)
return true;
}
+/* Handle calls to functions referenced by
+ __attribute__((malloc(FOO))). */
+
+void
+region_model::impl_deallocation_call (const call_details &cd)
+{
+ impl_call_free (cd);
+}
+
} // namespace ana
#endif /* #if ENABLE_ANALYZER */
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index 81e1a48..cfe8a39 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -65,6 +65,7 @@ along with GCC; see the file COPYING3. If not see
#include "analyzer/region-model-reachability.h"
#include "analyzer/analyzer-selftests.h"
#include "stor-layout.h"
+#include "attribs.h"
#if ENABLE_ANALYZER
@@ -917,6 +918,14 @@ region_model::on_call_post (const gcall *call,
impl_call_operator_delete (cd);
return;
}
+ /* Was this fndecl referenced by
+ __attribute__((malloc(FOO)))? */
+ if (lookup_attribute ("*dealloc", DECL_ATTRIBUTES (callee_fndecl)))
+ {
+ call_details cd (call, this, ctxt);
+ impl_deallocation_call (cd);
+ return;
+ }
}
if (unknown_side_effects)
diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h
index 0e303d0..efd1a09 100644
--- a/gcc/analyzer/region-model.h
+++ b/gcc/analyzer/region-model.h
@@ -463,6 +463,7 @@ class region_model
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 impl_deallocation_call (const call_details &cd);
void handle_unrecognized_call (const gcall *call,
region_model_context *ctxt);
diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc
index e1fea02..d7c76e3 100644
--- a/gcc/analyzer/sm-malloc.cc
+++ b/gcc/analyzer/sm-malloc.cc
@@ -42,6 +42,8 @@ along with GCC; see the file COPYING3. If not see
#include "analyzer/program-point.h"
#include "analyzer/store.h"
#include "analyzer/region-model.h"
+#include "stringpool.h"
+#include "attribs.h"
#if ENABLE_ANALYZER
@@ -49,14 +51,38 @@ namespace ana {
namespace {
-class api;
+/* This state machine and its various support classes track allocations
+ and deallocations.
+
+ It has a few standard allocation/deallocation pairs (e.g. new/delete),
+ and also supports user-defined ones via
+ __attribute__ ((malloc(DEALLOCATOR))).
+
+ There can be more than one valid deallocator for a given allocator,
+ for example:
+ __attribute__ ((malloc (fclose)))
+ __attribute__ ((malloc (freopen, 3)))
+ FILE* fopen (const char*, const char*);
+ A deallocator_set represents a particular set of valid deallocators.
+
+ We track the expected deallocator_set for a value, but not the allocation
+ function - there could be more than one allocator per deallocator_set.
+ 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. */
+
+class deallocator;
+class deallocator_set;
class malloc_state_machine;
/* An enum for discriminating between different kinds of allocation_state. */
enum resource_state
{
- /* States that are independent of api. */
+ /* States that are independent of allocator/deallocator. */
/* The start state. */
RS_START,
@@ -71,28 +97,33 @@ enum resource_state
/* Stop state, for pointers we don't want to track any more. */
RS_STOP,
- /* States that relate to a specific api. */
+ /* States that relate to a specific deallocator_set. */
- /* State for a pointer returned from the api's allocator that hasn't
+ /* State for a pointer returned from an 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,
+ /* State for a pointer returned from an allocator,
known to be non-NULL. */
RS_NONNULL,
- /* State for a pointer passed to the api's deallocator. */
+ /* State for a pointer passed to a deallocator. */
RS_FREED
};
-/* Custom state subclass, which can optionally refer to an an api. */
+/* Custom state subclass, which can optionally refer to an a
+ deallocator_set. */
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)
+ enum resource_state rs,
+ const deallocator_set *deallocators,
+ const deallocator *deallocator)
+ : state (name, id), m_rs (rs),
+ m_deallocators (deallocators),
+ m_deallocator (deallocator)
{}
void dump_to_pp (pretty_printer *pp) const FINAL OVERRIDE;
@@ -100,7 +131,8 @@ struct allocation_state : public state_machine::state
const allocation_state *get_nonnull () const;
enum resource_state m_rs;
- const api *m_api;
+ const deallocator_set *m_deallocators;
+ const deallocator *m_deallocator;
};
/* An enum for choosing which wording to use in various diagnostics
@@ -109,52 +141,186 @@ struct allocation_state : public state_machine::state
enum wording
{
WORDING_FREED,
- WORDING_DELETED
+ WORDING_DELETED,
+ WORDING_DEALLOCATED
};
-/* 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.
+/* Base class representing a deallocation function,
+ either a built-in one we know about, or one exposed via
+ __attribute__((malloc(DEALLOCATOR))). */
- 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
+struct deallocator
{
- api (malloc_state_machine *sm,
- const char *name,
- const char *dealloc_funcname,
- enum wording wording);
+ hashval_t hash () const;
+ void dump_to_pp (pretty_printer *pp) const;
+ static int cmp (const deallocator *a, const deallocator *b);
+ static int cmp_ptr_ptr (const void *, const void *);
- /* An internal name for identifying this API in dumps. */
+ /* Name to use in diagnostics. */
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;
+
+ /* State for a value passed to one of the deallocators. */
+ state_machine::state_t m_freed;
+
+protected:
+ deallocator (malloc_state_machine *sm,
+ const char *name,
+ enum wording wording);
+};
+
+/* Subclass representing a predefined deallocator.
+ e.g. "delete []", without needing a specific FUNCTION_DECL
+ ahead of time. */
+
+struct standard_deallocator : public deallocator
+{
+ standard_deallocator (malloc_state_machine *sm,
+ const char *name,
+ enum wording wording);
+};
+
+/* Subclass representing a user-defined deallocator
+ via __attribute__((malloc(DEALLOCATOR))) given
+ a specific FUNCTION_DECL. */
+
+struct custom_deallocator : public deallocator
+{
+ custom_deallocator (malloc_state_machine *sm,
+ tree deallocator_fndecl,
+ enum wording wording)
+ : deallocator (sm, IDENTIFIER_POINTER (DECL_NAME (deallocator_fndecl)),
+ wording)
+ {
+ }
+};
+
+/* Base class representing a set of possible deallocators.
+ Often this will be just a single deallocator, but some
+ allocators have multiple valid deallocators (e.g. the result of
+ "fopen" can be closed by either "fclose" or "freopen"). */
+
+struct deallocator_set
+{
+ deallocator_set (malloc_state_machine *sm,
+ enum wording wording);
+ virtual ~deallocator_set () {}
+
+ virtual bool contains_p (const deallocator *d) const = 0;
+ virtual const deallocator *maybe_get_single () const = 0;
+ virtual void dump_to_pp (pretty_printer *pp) const = 0;
+ void dump () const;
/* Which wording to use in diagnostics. */
enum wording m_wording;
- /* Pointers to api-specific states.
+ /* Pointers to states.
These states are owned by the state_machine base class. */
- /* State for an unchecked result from this api's allocator. */
+ /* State for an unchecked result from an allocator using this set. */
state_machine::state_t m_unchecked;
- /* State for a known non-NULL result from this apis's allocator. */
+ /* State for a known non-NULL result from such an allocator. */
state_machine::state_t m_nonnull;
+};
- /* State for a value passed to this api's deallocator. */
- state_machine::state_t m_freed;
+/* Subclass of deallocator_set representing a set of deallocators
+ defined by one or more __attribute__((malloc(DEALLOCATOR))). */
+
+struct custom_deallocator_set : public deallocator_set
+{
+ typedef const auto_vec <const deallocator *> *key_t;
+
+ custom_deallocator_set (malloc_state_machine *sm,
+ const auto_vec <const deallocator *> *vec,
+ //const char *name,
+ //const char *dealloc_funcname,
+ //unsigned arg_idx,
+ enum wording wording);
+
+ bool contains_p (const deallocator *d) const FINAL OVERRIDE;
+ const deallocator *maybe_get_single () const FINAL OVERRIDE;
+ void dump_to_pp (pretty_printer *pp) const FINAL OVERRIDE;
+
+ auto_vec <const deallocator *> m_deallocator_vec;
+};
+
+/* Subclass of deallocator_set representing a set of deallocators
+ with a single standard_deallocator, e.g. "delete []". */
+
+struct standard_deallocator_set : public deallocator_set
+{
+ standard_deallocator_set (malloc_state_machine *sm,
+ const char *name,
+ enum wording wording);
+
+ bool contains_p (const deallocator *d) const FINAL OVERRIDE;
+ const deallocator *maybe_get_single () const FINAL OVERRIDE;
+ void dump_to_pp (pretty_printer *pp) const FINAL OVERRIDE;
+
+ standard_deallocator m_deallocator;
+};
+
+/* Traits class for ensuring uniqueness of deallocator_sets within
+ malloc_state_machine. */
+
+struct deallocator_set_map_traits
+{
+ typedef custom_deallocator_set::key_t key_type;
+ typedef custom_deallocator_set *value_type;
+ typedef custom_deallocator_set *compare_type;
+
+ static inline hashval_t hash (const key_type &k)
+ {
+ gcc_assert (k != NULL);
+ gcc_assert (k != reinterpret_cast<key_type> (1));
+
+ hashval_t result = 0;
+ unsigned i;
+ const deallocator *d;
+ FOR_EACH_VEC_ELT (*k, i, d)
+ result ^= d->hash ();
+ return result;
+ }
+ static inline bool equal_keys (const key_type &k1, const key_type &k2)
+ {
+ if (k1->length () != k2->length ())
+ return false;
+
+ for (unsigned i = 0; i < k1->length (); i++)
+ if ((*k1)[i] != (*k2)[i])
+ return false;
+
+ return true;
+ }
+ template <typename T>
+ static inline void remove (T &)
+ {
+ /* empty; the nodes are handled elsewhere. */
+ }
+ template <typename T>
+ static inline void mark_deleted (T &entry)
+ {
+ entry.m_key = reinterpret_cast<key_type> (1);
+ }
+ template <typename T>
+ static inline void mark_empty (T &entry)
+ {
+ entry.m_key = NULL;
+ }
+ template <typename T>
+ static inline bool is_deleted (const T &entry)
+ {
+ return entry.m_key == reinterpret_cast<key_type> (1);
+ }
+ template <typename T>
+ static inline bool is_empty (const T &entry)
+ {
+ return entry.m_key == NULL;
+ }
+ static const bool empty_zero_p = false;
};
/* A state machine for detecting misuses of the malloc/free API.
@@ -167,9 +333,12 @@ public:
typedef allocation_state custom_data_t;
malloc_state_machine (logger *logger);
+ ~malloc_state_machine ();
state_t
- add_state (const char *name, enum resource_state rs, const api *a);
+ add_state (const char *name, enum resource_state rs,
+ const deallocator_set *deallocators,
+ const deallocator *deallocator);
bool inherited_state_p () const FINAL OVERRIDE { return false; }
@@ -214,9 +383,9 @@ public:
bool reset_when_passed_to_unknown_fn_p (state_t s,
bool is_mutable) const FINAL OVERRIDE;
- api m_malloc;
- api m_scalar_new;
- api m_vector_new;
+ standard_deallocator_set m_free;
+ standard_deallocator_set m_scalar_delete;
+ standard_deallocator_set m_vector_delete;
/* States that are independent of api. */
@@ -232,33 +401,194 @@ public:
state_t m_stop;
private:
+ const custom_deallocator_set *
+ get_or_create_custom_deallocator_set (tree allocator_fndecl);
+ custom_deallocator_set *
+ maybe_create_custom_deallocator_set (tree allocator_fndecl);
+ const deallocator *
+ get_or_create_deallocator (tree deallocator_fndecl);
+
void on_allocator_call (sm_context *sm_ctxt,
const gcall *call,
- const api &ap) const;
+ const deallocator_set *deallocators,
+ bool returns_nonnull = false) const;
void on_deallocator_call (sm_context *sm_ctxt,
const supernode *node,
const gcall *call,
- const api &ap) const;
+ const deallocator *d,
+ unsigned argno) const;
void on_zero_assignment (sm_context *sm_ctxt,
const gimple *stmt,
tree lhs) const;
+
+ /* A map for consolidating deallocators so that they are
+ unique per deallocator FUNCTION_DECL. */
+ typedef hash_map<tree, deallocator *> deallocator_map_t;
+ deallocator_map_t m_deallocator_map;
+
+ /* Memoized lookups from FUNCTION_DECL to custom_deallocator_set *. */
+ typedef hash_map<tree, custom_deallocator_set *> deallocator_set_cache_t;
+ deallocator_set_cache_t m_custom_deallocator_set_cache;
+
+ /* A map for consolidating custom_deallocator_set instances. */
+ typedef hash_map<custom_deallocator_set::key_t,
+ custom_deallocator_set *,
+ deallocator_set_map_traits> custom_deallocator_set_map_t;
+ custom_deallocator_set_map_t m_custom_deallocator_set_map;
+
+ /* Record of dynamically-allocated objects, for cleanup. */
+ auto_vec <custom_deallocator_set *> m_dynamic_sets;
+ auto_vec <custom_deallocator *> m_dynamic_deallocators;
};
-/* struct api. */
+/* struct deallocator. */
-api::api (malloc_state_machine *sm,
- const char *name,
- const char *dealloc_funcname,
- enum wording wording)
+deallocator::deallocator (malloc_state_machine *sm,
+ const char *name,
+ 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))
+ m_freed (sm->add_state ("freed", RS_FREED, NULL, this))
+{
+}
+
+hashval_t
+deallocator::hash () const
+{
+ return (hashval_t)m_freed->get_id ();
+}
+
+void
+deallocator::dump_to_pp (pretty_printer *pp) const
+{
+ pp_printf (pp, "%qs", m_name);
+}
+
+int
+deallocator::cmp (const deallocator *a, const deallocator *b)
+{
+ return (int)a->m_freed->get_id () - (int)b->m_freed->get_id ();
+}
+
+int
+deallocator::cmp_ptr_ptr (const void *a, const void *b)
+{
+ return cmp (*(const deallocator * const *)a,
+ *(const deallocator * const *)b);
+}
+
+
+/* struct standard_deallocator : public deallocator. */
+
+standard_deallocator::standard_deallocator (malloc_state_machine *sm,
+ const char *name,
+ enum wording wording)
+: deallocator (sm, name, wording)
+{
+}
+
+/* struct deallocator_set. */
+
+deallocator_set::deallocator_set (malloc_state_machine *sm,
+ enum wording wording)
+: m_wording (wording),
+ m_unchecked (sm->add_state ("unchecked", RS_UNCHECKED, this, NULL)),
+ m_nonnull (sm->add_state ("nonnull", RS_NONNULL, this, NULL))
+{
+}
+
+/* Dump a description of this deallocator_set to stderr. */
+
+DEBUG_FUNCTION void
+deallocator_set::dump () const
+{
+ pretty_printer pp;
+ pp_show_color (&pp) = pp_show_color (global_dc->printer);
+ pp.buffer->stream = stderr;
+ dump_to_pp (&pp);
+ pp_newline (&pp);
+ pp_flush (&pp);
+}
+
+/* struct custom_deallocator_set : public deallocator_set. */
+
+custom_deallocator_set::
+custom_deallocator_set (malloc_state_machine *sm,
+ const auto_vec <const deallocator *> *vec,
+ enum wording wording)
+: deallocator_set (sm, wording),
+ m_deallocator_vec (vec->length ())
+{
+ unsigned i;
+ const deallocator *d;
+ FOR_EACH_VEC_ELT (*vec, i, d)
+ m_deallocator_vec.safe_push (d);
+}
+
+bool
+custom_deallocator_set::contains_p (const deallocator *d) const
+{
+ unsigned i;
+ const deallocator *cd;
+ FOR_EACH_VEC_ELT (m_deallocator_vec, i, cd)
+ if (cd == d)
+ return true;
+ return false;
+}
+
+const deallocator *
+custom_deallocator_set::maybe_get_single () const
+{
+ if (m_deallocator_vec.length () == 1)
+ return m_deallocator_vec[0];
+ return NULL;
+}
+
+void
+custom_deallocator_set::dump_to_pp (pretty_printer *pp) const
+{
+ pp_character (pp, '{');
+ unsigned i;
+ const deallocator *d;
+ FOR_EACH_VEC_ELT (m_deallocator_vec, i, d)
+ {
+ if (i > 0)
+ pp_string (pp, ", ");
+ d->dump_to_pp (pp);
+ }
+ pp_character (pp, '}');
+}
+
+/* struct standard_deallocator_set : public deallocator_set. */
+
+standard_deallocator_set::standard_deallocator_set (malloc_state_machine *sm,
+ const char *name,
+ enum wording wording)
+: deallocator_set (sm, wording),
+ m_deallocator (sm, name, wording)
{
}
+bool
+standard_deallocator_set::contains_p (const deallocator *d) const
+{
+ return d == &m_deallocator;
+}
+
+const deallocator *
+standard_deallocator_set::maybe_get_single () const
+{
+ return &m_deallocator;
+}
+
+void
+standard_deallocator_set::dump_to_pp (pretty_printer *pp) const
+{
+ pp_character (pp, '{');
+ pp_string (pp, m_deallocator.m_name);
+ pp_character (pp, '}');
+}
+
/* 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. */
@@ -291,6 +621,14 @@ get_rs (state_machine::state_t state)
return RS_START;
}
+/* Return true if STATE is the start state. */
+
+static bool
+start_p (state_machine::state_t state)
+{
+ return get_rs (state) == RS_START;
+}
+
/* Return true if STATE is an unchecked result from an allocator. */
static bool
@@ -383,10 +721,10 @@ class mismatching_deallocation : public malloc_diagnostic
{
public:
mismatching_deallocation (const malloc_state_machine &sm, tree arg,
- const api *expected_dealloc,
- const api *actual_dealloc)
+ const deallocator_set *expected_deallocators,
+ const deallocator *actual_dealloc)
: malloc_diagnostic (sm, arg),
- m_expected_dealloc (expected_dealloc),
+ m_expected_deallocators (expected_deallocators),
m_actual_dealloc (actual_dealloc)
{}
@@ -400,11 +738,18 @@ public:
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);
+ if (const deallocator *expected_dealloc
+ = m_expected_deallocators->maybe_get_single ())
+ return warning_meta (rich_loc, m, OPT_Wanalyzer_mismatching_deallocation,
+ "%qE should have been deallocated with %qs"
+ " but was deallocated with %qs",
+ m_arg, expected_dealloc->m_name,
+ m_actual_dealloc->m_name);
+ else
+ return warning_meta (rich_loc, m, OPT_Wanalyzer_mismatching_deallocation,
+ "%qs called on %qE returned from a mismatched"
+ " allocation function",
+ m_actual_dealloc->m_name, m_arg);
}
label_text describe_state_change (const evdesc::state_change &change)
@@ -413,9 +758,13 @@ public:
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);
+ if (const deallocator *expected_dealloc
+ = m_expected_deallocators->maybe_get_single ())
+ return change.formatted_print ("allocated here"
+ " (expects deallocation with %qs)",
+ expected_dealloc->m_name);
+ else
+ return change.formatted_print ("allocated here");
}
return malloc_diagnostic::describe_state_change (change);
}
@@ -423,19 +772,28 @@ public:
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);
+ {
+ if (const deallocator *expected_dealloc
+ = m_expected_deallocators->maybe_get_single ())
+ return ev.formatted_print
+ ("deallocated with %qs here;"
+ " allocation at %@ expects deallocation with %qs",
+ m_actual_dealloc->m_name, &m_alloc_event,
+ expected_dealloc->m_name);
+ else
+ return ev.formatted_print
+ ("deallocated with %qs here;"
+ " allocated at %@",
+ m_actual_dealloc->m_name, &m_alloc_event);
+ }
return ev.formatted_print ("deallocated with %qs here",
- m_actual_dealloc->m_dealloc_funcname);
+ m_actual_dealloc->m_name);
}
private:
diagnostic_event_id_t m_alloc_event;
- const api *m_expected_dealloc;
- const api *m_actual_dealloc;
+ const deallocator_set *m_expected_deallocators;
+ const deallocator *m_actual_dealloc;
};
/* Concrete subclass for reporting double-free diagnostics. */
@@ -455,7 +813,7 @@ public:
diagnostic_metadata m;
m.add_cwe (415); /* CWE-415: Double Free. */
return warning_meta (rich_loc, m, OPT_Wanalyzer_double_free,
- "double-%<%s%> of %qE", m_funcname, m_arg);
+ "double-%qs of %qE", m_funcname, m_arg);
}
label_text describe_state_change (const evdesc::state_change &change)
@@ -765,9 +1123,12 @@ class use_after_free : public malloc_diagnostic
{
public:
use_after_free (const malloc_state_machine &sm, tree arg,
- const api *a)
- : malloc_diagnostic (sm, arg), m_api (a)
- {}
+ const deallocator *deallocator)
+ : malloc_diagnostic (sm, arg),
+ m_deallocator (deallocator)
+ {
+ gcc_assert (deallocator);
+ }
const char *get_kind () const FINAL OVERRIDE { return "use_after_free"; }
@@ -778,7 +1139,7 @@ public:
m.add_cwe (416);
return warning_meta (rich_loc, m, OPT_Wanalyzer_use_after_free,
"use after %<%s%> of %qE",
- m_api->m_dealloc_funcname, m_arg);
+ m_deallocator->m_name, m_arg);
}
label_text describe_state_change (const evdesc::state_change &change)
@@ -787,7 +1148,7 @@ public:
if (freed_p (change.m_new_state))
{
m_free_event = change.m_event_id;
- switch (m_api->m_wording)
+ switch (m_deallocator->m_wording)
{
default:
gcc_unreachable ();
@@ -795,6 +1156,8 @@ public:
return label_text::borrow ("freed here");
case WORDING_DELETED:
return label_text::borrow ("deleted here");
+ case WORDING_DEALLOCATED:
+ return label_text::borrow ("deallocated here");
}
}
return malloc_diagnostic::describe_state_change (change);
@@ -802,9 +1165,9 @@ public:
label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
{
- const char *funcname = m_api->m_dealloc_funcname;
+ const char *funcname = m_deallocator->m_name;
if (m_free_event.known_p ())
- switch (m_api->m_wording)
+ switch (m_deallocator->m_wording)
{
default:
gcc_unreachable ();
@@ -814,6 +1177,10 @@ public:
case WORDING_DELETED:
return ev.formatted_print ("use after %<%s%> of %qE; deleted at %@",
funcname, ev.m_expr, &m_free_event);
+ case WORDING_DEALLOCATED:
+ return ev.formatted_print ("use after %<%s%> of %qE;"
+ " deallocated at %@",
+ funcname, ev.m_expr, &m_free_event);
}
else
return ev.formatted_print ("use after %<%s%> of %qE",
@@ -822,7 +1189,7 @@ public:
private:
diagnostic_event_id_t m_free_event;
- const api *m_api;
+ const deallocator *m_deallocator;
};
class malloc_leak : public malloc_diagnostic
@@ -848,7 +1215,8 @@ public:
label_text describe_state_change (const evdesc::state_change &change)
FINAL OVERRIDE
{
- if (unchecked_p (change.m_new_state))
+ if (unchecked_p (change.m_new_state)
+ || (start_p (change.m_old_state) && nonnull_p (change.m_new_state)))
{
m_alloc_event = change.m_event_id;
return label_text::borrow ("allocated here");
@@ -969,40 +1337,161 @@ 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);
+ if (m_deallocators)
+ {
+ pp_string (pp, " (");
+ m_deallocators->dump_to_pp (pp);
+ pp_character (pp, ')');
+ }
}
-/* Given a allocation_state for an api, get the "nonnull" state
- for the corresponding allocator. */
+/* Given a allocation_state for a deallocator_set, get the "nonnull" state
+ for the corresponding allocator(s). */
const allocation_state *
allocation_state::get_nonnull () const
{
- gcc_assert (m_api);
- return as_a_allocation_state (m_api->m_nonnull);
+ gcc_assert (m_deallocators);
+ return as_a_allocation_state (m_deallocators->m_nonnull);
}
/* malloc_state_machine's ctor. */
malloc_state_machine::malloc_state_machine (logger *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_free (this, "free", WORDING_FREED),
+ m_scalar_delete (this, "delete", WORDING_DELETED),
+ m_vector_delete (this, "delete[]", WORDING_DELETED)
{
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);
+ m_null = add_state ("null", RS_FREED, NULL, NULL);
+ m_non_heap = add_state ("non-heap", RS_NON_HEAP, NULL, NULL);
+ m_stop = add_state ("stop", RS_STOP, NULL, NULL);
+}
+
+malloc_state_machine::~malloc_state_machine ()
+{
+ unsigned i;
+ custom_deallocator_set *set;
+ FOR_EACH_VEC_ELT (m_dynamic_sets, i, set)
+ delete set;
+ custom_deallocator *d;
+ FOR_EACH_VEC_ELT (m_dynamic_deallocators, i, d)
+ delete d;
}
state_machine::state_t
malloc_state_machine::add_state (const char *name, enum resource_state rs,
- const api *a)
+ const deallocator_set *deallocators,
+ const deallocator *deallocator)
{
return add_custom_state (new allocation_state (name, alloc_state_id (),
- rs, a));
+ rs, deallocators,
+ deallocator));
+}
+
+/* If ALLOCATOR_FNDECL has any "__attribute__((malloc(FOO)))",
+ return a custom_deallocator_set for them, consolidating them
+ to ensure uniqueness of the sets.
+
+ Return NULL if it has no such attributes. */
+
+const custom_deallocator_set *
+malloc_state_machine::
+get_or_create_custom_deallocator_set (tree allocator_fndecl)
+{
+ /* Early rejection of decls without attributes. */
+ tree attrs = DECL_ATTRIBUTES (allocator_fndecl);
+ if (!attrs)
+ return NULL;
+
+ /* Otherwise, call maybe_create_custom_deallocator_set,
+ memoizing the result. */
+ if (custom_deallocator_set **slot
+ = m_custom_deallocator_set_cache.get (allocator_fndecl))
+ return *slot;
+ custom_deallocator_set *set
+ = maybe_create_custom_deallocator_set (allocator_fndecl);
+ m_custom_deallocator_set_cache.put (allocator_fndecl, set);
+ return set;
+}
+
+/* Given ALLOCATOR_FNDECL, a FUNCTION_DECL with attributes,
+ look for any "__attribute__((malloc(FOO)))" and return a
+ custom_deallocator_set for them, consolidating them
+ to ensure uniqueness of the sets.
+
+ Return NULL if it has no such attributes.
+
+ Subroutine of get_or_create_custom_deallocator_set which
+ memoizes the result. */
+
+custom_deallocator_set *
+malloc_state_machine::
+maybe_create_custom_deallocator_set (tree allocator_fndecl)
+{
+ tree attrs = DECL_ATTRIBUTES (allocator_fndecl);
+ gcc_assert (attrs);
+
+ /* Look for instances of __attribute__((malloc(FOO))). */
+ auto_vec<const deallocator *> deallocator_vec;
+ for (tree allocs = attrs;
+ (allocs = lookup_attribute ("malloc", allocs));
+ allocs = TREE_CHAIN (allocs))
+ {
+ tree args = TREE_VALUE (allocs);
+ if (!args)
+ continue;
+ if (TREE_VALUE (args))
+ {
+ const deallocator *d
+ = get_or_create_deallocator (TREE_VALUE (args));
+ deallocator_vec.safe_push (d);
+ }
+ }
+
+ /* If there weren't any deallocators, bail. */
+ if (deallocator_vec.length () == 0)
+ return NULL;
+
+ /* Consolidate, so that we reuse existing deallocator_set
+ instances. */
+ deallocator_vec.qsort (deallocator::cmp_ptr_ptr);
+ custom_deallocator_set **slot
+ = m_custom_deallocator_set_map.get (&deallocator_vec);
+ if (slot)
+ return *slot;
+ custom_deallocator_set *set
+ = new custom_deallocator_set (this, &deallocator_vec, WORDING_DEALLOCATED);
+ m_custom_deallocator_set_map.put (&set->m_deallocator_vec, set);
+ m_dynamic_sets.safe_push (set);
+ return set;
+}
+
+/* Get the deallocator for DEALLOCATOR_FNDECL, creating it if necessary. */
+
+const deallocator *
+malloc_state_machine::get_or_create_deallocator (tree deallocator_fndecl)
+{
+ deallocator **slot = m_deallocator_map.get (deallocator_fndecl);
+ if (slot)
+ return *slot;
+
+ /* Reuse "free". */
+ deallocator *d;
+ if (is_named_call_p (deallocator_fndecl, "free")
+ || is_std_named_call_p (deallocator_fndecl, "free"))
+ d = &m_free.m_deallocator;
+ else
+ {
+ custom_deallocator *cd
+ = new custom_deallocator (this, deallocator_fndecl,
+ WORDING_DEALLOCATED);
+ m_dynamic_deallocators.safe_push (cd);
+ d = cd;
+ }
+ m_deallocator_map.put (deallocator_fndecl, d);
+ return d;
}
/* Implementation of state_machine::on_stmt vfunc for malloc_state_machine. */
@@ -1024,23 +1513,25 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
|| is_named_call_p (callee_fndecl, "strdup", call, 1)
|| is_named_call_p (callee_fndecl, "strndup", call, 2))
{
- on_allocator_call (sm_ctxt, call, m_malloc);
+ on_allocator_call (sm_ctxt, call, &m_free);
return true;
}
if (is_named_call_p (callee_fndecl, "operator new", call, 1))
- on_allocator_call (sm_ctxt, call, m_scalar_new);
+ on_allocator_call (sm_ctxt, call, &m_scalar_delete);
else if (is_named_call_p (callee_fndecl, "operator new []", call, 1))
- on_allocator_call (sm_ctxt, call, m_vector_new);
+ on_allocator_call (sm_ctxt, call, &m_vector_delete);
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);
+ on_deallocator_call (sm_ctxt, node, call,
+ &m_scalar_delete.m_deallocator, 0);
return true;
}
else if (is_named_call_p (callee_fndecl, "operator delete []", call, 1))
{
- on_deallocator_call (sm_ctxt, node, call, m_vector_new);
+ on_deallocator_call (sm_ctxt, node, call,
+ &m_vector_delete.m_deallocator, 0);
return true;
}
@@ -1057,10 +1548,26 @@ 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))
{
- on_deallocator_call (sm_ctxt, node, call, m_malloc);
+ on_deallocator_call (sm_ctxt, node, call,
+ &m_free.m_deallocator, 0);
return true;
}
+ /* Cast away const-ness for cache-like operations. */
+ malloc_state_machine *mutable_this
+ = const_cast <malloc_state_machine *> (this);
+
+ /* Handle "__attribute__((malloc(FOO)))". */
+ if (const deallocator_set *deallocators
+ = mutable_this->get_or_create_custom_deallocator_set
+ (callee_fndecl))
+ {
+ tree attrs = TYPE_ATTRIBUTES (TREE_TYPE (callee_fndecl));
+ bool returns_nonnull
+ = lookup_attribute ("returns_nonnull", attrs);
+ on_allocator_call (sm_ctxt, call, deallocators, returns_nonnull);
+ }
+
/* Handle "__attribute__((nonnull))". */
{
tree fntype = TREE_TYPE (callee_fndecl);
@@ -1103,6 +1610,16 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
BITMAP_FREE (nonnull_args);
}
}
+
+ /* Check for this after nonnull, so that if we have both
+ then we transition to "freed", rather than "checked". */
+ unsigned dealloc_argno = fndecl_dealloc_argno (callee_fndecl);
+ if (dealloc_argno != UINT_MAX)
+ {
+ const deallocator *d
+ = mutable_this->get_or_create_deallocator (callee_fndecl);
+ on_deallocator_call (sm_ctxt, node, call, d, dealloc_argno);
+ }
}
if (tree lhs = sm_ctxt->is_zero_assignment (stmt))
@@ -1162,7 +1679,7 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
const allocation_state *astate = as_a_allocation_state (state);
sm_ctxt->warn (node, stmt, arg,
new use_after_free (*this, diag_arg,
- astate->m_api));
+ astate->m_deallocator));
sm_ctxt->set_next_state (stmt, arg, m_stop);
}
}
@@ -1170,18 +1687,24 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
return false;
}
-/* Handle a call to an allocator. */
+/* Handle a call to an allocator.
+ RETURNS_NONNULL is true if CALL is to a fndecl known to have
+ __attribute__((returns_nonnull)). */
void
malloc_state_machine::on_allocator_call (sm_context *sm_ctxt,
const gcall *call,
- const api &ap) const
+ const deallocator_set *deallocators,
+ bool returns_nonnull) 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);
+ sm_ctxt->set_next_state (call, lhs,
+ (returns_nonnull
+ ? deallocators->m_nonnull
+ : deallocators->m_unchecked));
}
else
{
@@ -1193,40 +1716,42 @@ void
malloc_state_machine::on_deallocator_call (sm_context *sm_ctxt,
const supernode *node,
const gcall *call,
- const api &ap) const
+ const deallocator *d,
+ unsigned argno) const
{
- tree arg = gimple_call_arg (call, 0);
+ if (argno >= gimple_call_num_args (call))
+ return;
+ tree arg = gimple_call_arg (call, argno);
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);
+ sm_ctxt->set_next_state (call, arg, d->m_freed);
else if (unchecked_p (state) || nonnull_p (state))
{
const allocation_state *astate = as_a_allocation_state (state);
-
- if (astate->m_api != &ap)
+ gcc_assert (astate->m_deallocators);
+ if (!astate->m_deallocators->contains_p (d))
{
/* Wrong allocator. */
- pending_diagnostic *d
+ pending_diagnostic *pd
= new mismatching_deallocation (*this, diag_arg,
- astate->m_api, &ap);
- sm_ctxt->warn (node, call, arg, d);
+ astate->m_deallocators,
+ d);
+ sm_ctxt->warn (node, call, arg, pd);
}
- sm_ctxt->set_next_state (call, arg, ap.m_freed);
+ sm_ctxt->set_next_state (call, arg, d->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)
+ else if (state == d->m_freed)
{
/* freed -> stop, with warning. */
sm_ctxt->warn (node, call, arg,
- new double_free (*this, diag_arg,
- ap.m_dealloc_funcname));
+ new double_free (*this, diag_arg, d->m_name));
sm_ctxt->set_next_state (call, arg, m_stop);
}
else if (state == m_non_heap)
@@ -1234,7 +1759,7 @@ malloc_state_machine::on_deallocator_call (sm_context *sm_ctxt,
/* non-heap -> stop, with warning. */
sm_ctxt->warn (node, call, arg,
new free_of_non_heap (*this, diag_arg,
- ap.m_dealloc_funcname));
+ d->m_name));
sm_ctxt->set_next_state (call, arg, m_stop);
}
}
diff --git a/gcc/attribs.h b/gcc/attribs.h
index 9e3e56a..21d28a4 100644
--- a/gcc/attribs.h
+++ b/gcc/attribs.h
@@ -310,4 +310,6 @@ extern void init_attr_rdwr_indices (rdwr_map *, tree);
extern attr_access *get_parm_access (rdwr_map &, tree,
tree = current_function_decl);
+extern unsigned fndecl_dealloc_argno (tree fndecl);
+
#endif // GCC_ATTRIBS_H
diff --git a/gcc/builtins.c b/gcc/builtins.c
index 02e7815..c1115a3 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -13014,6 +13014,16 @@ call_dealloc_argno (tree exp)
if (!fndecl)
return UINT_MAX;
+ return fndecl_dealloc_argno (fndecl);
+}
+
+/* Return the zero-based number corresponding to the argument being
+ deallocated if FNDECL is a deallocation function or UINT_MAX
+ if it isn't. */
+
+unsigned
+fndecl_dealloc_argno (tree fndecl)
+{
/* A call to operator delete isn't recognized as one to a built-in. */
if (DECL_IS_OPERATOR_DELETE_P (fndecl))
return 0;
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index c5b1faf..8daa1c6 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -3291,6 +3291,58 @@ __attribute__ ((malloc, malloc (fclose (1))))
FILE* tmpfile (void);
@end smallexample
+The warnings guarded by @option{-fanalyzer} respect allocation and
+deallocation pairs marked with the @code{malloc}. In particular:
+
+@itemize @bullet
+
+@item
+The analyzer will emit a @option{-Wanalyzer-mismatching-deallocation}
+diagnostic if there is an execution path in which the result of an
+allocation call is passed to a different deallocator.
+
+@item
+The analyzer will emit a @option{-Wanalyzer-double-free}
+diagnostic if there is an execution path in which a value is passed
+more than once to a deallocation call.
+
+@item
+The analyzer will consider the possibility that an allocation function
+could fail and return NULL. It will emit
+@option{-Wanalyzer-possible-null-dereference} and
+@option{-Wanalyzer-possible-null-argument} diagnostics if there are
+execution paths in which an unchecked result of an allocation call is
+dereferenced or passed to a function requiring a non-null argument.
+If the allocator always returns non-null, use
+@code{__attribute__ ((returns_nonnull))} to suppress these warnings.
+For example:
+@smallexample
+char *xstrdup (const char *)
+ __attribute__((malloc (free), returns_nonnull));
+@end smallexample
+
+@item
+The analyzer will emit a @option{-Wanalyzer-use-after-free}
+diagnostic if there is an execution path in which the memory passed
+by pointer to a deallocation call is used after the deallocation.
+
+@item
+The analyzer will emit a @option{-Wanalyzer-malloc-leak} diagnostic if
+there is an execution path in which the result of an allocation call
+is leaked (without being passed to the deallocation function).
+
+@item
+The analyzer will emit a @option{-Wanalyzer-free-of-non-heap} diagnostic
+if a deallocation function is used on a global or on-stack variable.
+
+@end itemize
+
+The analyzer assumes that deallocators can gracefully handle the @code{NULL}
+pointer. If this is not the case, the deallocator can be marked with
+@code{__attribute__((nonnull))} so that @option{-fanalyzer} can emit
+a @option{-Wanalyzer-possible-null-argument} diagnostic for code paths
+in which the deallocator is called with NULL.
+
@item no_icf
@cindex @code{no_icf} function attribute
This function attribute prevents a functions from being merged with another
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index c290b6f..5077ea7 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -9155,7 +9155,8 @@ This warning requires @option{-fanalyzer}, which enables it; use
@option{-Wno-analyzer-double-free} to disable it.
This diagnostic warns for paths through the code in which a pointer
-can have @code{free} called on it more than once.
+can have a deallocator called on it more than once, either @code{free},
+or a deallocator referenced by attribute @code{malloc}.
@item -Wno-analyzer-exposure-through-output-file
@opindex Wanalyzer-exposure-through-output-file
@@ -9196,7 +9197,8 @@ This warning requires @option{-fanalyzer}, which enables it; use
to disable it.
This diagnostic warns for paths through the code in which a
-pointer allocated via @code{malloc} is leaked.
+pointer allocated via an allocator is leaked: either @code{malloc},
+or a function marked with attribute @code{malloc}.
@item -Wno-analyzer-mismatching-deallocation
@opindex Wanalyzer-mismatching-deallocation
@@ -9207,7 +9209,10 @@ 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.
+which function was used to allocate the pointer value. The diagnostic
+will warn about mismatches between @code{free}, scalar @code{delete}
+and vector @code{delete[]}, and those marked as allocator/deallocator
+pairs using attribute @code{malloc}.
@item -Wno-analyzer-possible-null-argument
@opindex Wanalyzer-possible-null-argument
@@ -9322,7 +9327,8 @@ This warning requires @option{-fanalyzer}, which enables it; use
@option{-Wno-analyzer-use-after-free} to disable it.
This diagnostic warns for paths through the code in which a
-pointer is used after @code{free} is called on it.
+pointer is used after a deallocator is called on it: either @code{free},
+or a deallocator referenced by attribute @code{malloc}.
@item -Wno-analyzer-use-of-pointer-in-stale-stack-frame
@opindex Wanalyzer-use-of-pointer-in-stale-stack-frame
diff --git a/gcc/testsuite/gcc.dg/analyzer/attr-malloc-1.c b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-1.c
new file mode 100644
index 0000000..3de32b1
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-1.c
@@ -0,0 +1,75 @@
+extern void free (void *);
+
+struct foo
+{
+ int m_int;
+};
+
+extern void foo_release (struct foo *);
+extern struct foo *foo_acquire (void)
+ __attribute__ ((malloc (foo_release)));
+extern void use_foo (const struct foo *)
+ __attribute__((nonnull));
+
+void test_1 (void)
+{
+ struct foo *p = foo_acquire ();
+ foo_release (p);
+}
+
+void test_2 (void)
+{
+ struct foo *p = foo_acquire (); /* { dg-message "this call could return NULL" } */
+ p->m_int = 42; /* { dg-warning "dereference of possibly-NULL 'p'" } */
+ foo_release (p);
+}
+
+void test_2a (void)
+{
+ struct foo *p = foo_acquire (); /* { dg-message "this call could return NULL" } */
+ use_foo (p); /* { dg-warning "use of possibly-NULL 'p' where non-null expected" } */
+ foo_release (p);
+}
+
+void test_3 (void)
+{
+ struct foo *p = foo_acquire (); /* { dg-message "allocated here" } */
+} /* { dg-warning "leak of 'p'" } */
+
+void test_4 (struct foo *p)
+{
+ foo_release (p);
+ foo_release (p); /* { dg-warning "double-'foo_release' of 'p'" } */
+}
+
+void test_4a (void)
+{
+ struct foo *p = foo_acquire ();
+ foo_release (p);
+ foo_release (p); /* { dg-warning "double-'foo_release' of 'p'" } */
+}
+
+void test_5 (void)
+{
+ struct foo *p = foo_acquire (); /* { dg-message "allocated here \\(expects deallocation with 'foo_release'\\)" } */
+ free (p); /* { dg-warning "'p' should have been deallocated with 'foo_release' but was deallocated with 'free'" } */
+}
+
+void test_6 (struct foo *p)
+{
+ foo_release (p);
+ free (p); // TODO: double-release warning!
+}
+
+void test_7 ()
+{
+ struct foo f;
+ foo_release (&f); /* { dg-warning "not on the heap" "analyzer" } */
+ /* { dg-warning "'foo_release' called on unallocated object 'f'" "non-analyzer" { target *-*-* } .-1 } */
+}
+
+int test_8 (struct foo *p)
+{
+ foo_release (p);
+ return p->m_int; /* { dg-warning "use after 'foo_release' of 'p'" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/attr-malloc-2.c b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-2.c
new file mode 100644
index 0000000..09d941f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-2.c
@@ -0,0 +1,24 @@
+extern void free (void *);
+char *xstrdup (const char *)
+ __attribute__((malloc (free), returns_nonnull));
+
+void test_1 (const char *s)
+{
+ char *p = xstrdup (s);
+ free (p);
+}
+
+/* Verify that we don't issue -Wanalyzer-possible-null-dereference
+ when the allocator has __attribute__((returns_nonnull)). */
+
+char *test_2 (const char *s)
+{
+ char *p = xstrdup (s);
+ p[0] = 'a'; /* { dg-bogus "possibly-NULL" } */
+ return p;
+}
+
+void test_3 (const char *s)
+{
+ char *p = xstrdup (s); /* { dg-message "allocated here" } */
+} /* { dg-warning "leak of 'p'" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/attr-malloc-4.c b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-4.c
new file mode 100644
index 0000000..1517667
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-4.c
@@ -0,0 +1,21 @@
+/* An example where the deallocator requires non-NULL. */
+
+struct foo;
+extern void foo_release (struct foo *)
+ __attribute__((nonnull));
+extern struct foo *foo_acquire (void)
+ __attribute__ ((malloc (foo_release)));
+
+void test_1 (void)
+{
+ struct foo *p = foo_acquire (); /* { dg-message "this call could return NULL" } */
+ foo_release (p); /* { dg-warning "use of possibly-NULL 'p' where non-null" } */
+}
+
+void test_2 (void)
+{
+ struct foo *p = foo_acquire ();
+ if (!p)
+ return;
+ foo_release (p);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/attr-malloc-5.c b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-5.c
new file mode 100644
index 0000000..7ff4e57
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-5.c
@@ -0,0 +1,12 @@
+/* Example of extra argument to "malloc" attribute. */
+
+struct foo;
+extern void foo_release (int, struct foo *);
+extern struct foo *foo_acquire (void)
+ __attribute__ ((malloc (foo_release, 2)));
+
+void test_1 (void)
+{
+ struct foo *p = foo_acquire ();
+ foo_release (0, p);
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/attr-malloc-6.c b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-6.c
new file mode 100644
index 0000000..bd28107
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-6.c
@@ -0,0 +1,228 @@
+/* Adapted from gcc.dg/Wmismatched-dealloc.c. */
+
+#define A(...) __attribute__ ((malloc (__VA_ARGS__)))
+
+typedef struct FILE FILE;
+typedef __SIZE_TYPE__ size_t;
+
+void free (void*);
+void* malloc (size_t);
+void* realloc (void*, size_t);
+
+int fclose (FILE*);
+FILE* freopen (const char*, const char*, FILE*);
+int pclose (FILE*);
+
+A (fclose) A (freopen, 3)
+ FILE* fdopen (int);
+A (fclose) A (freopen, 3)
+ FILE* fopen (const char*, const char*);
+A (fclose) A (freopen, 3)
+ FILE* fmemopen(void *, size_t, const char *);
+A (fclose) A (freopen, 3)
+ FILE* freopen (const char*, const char*, FILE*);
+A (pclose) A (freopen, 3)
+ FILE* popen (const char*, const char*);
+A (fclose) A (freopen, 3)
+ FILE* tmpfile (void);
+
+void sink (FILE*);
+
+
+ void release (void*);
+A (release) FILE* acquire (void);
+
+void nowarn_fdopen (void)
+{
+ {
+ FILE *q = fdopen (0);
+ if (!q)
+ return;
+
+ fclose (q);
+ }
+
+ {
+ FILE *q = fdopen (0);
+ if (!q)
+ return;
+
+ q = freopen ("1", "r", q);
+ fclose (q);
+ }
+
+ {
+ FILE *q = fdopen (0);
+ if (!q)
+ return;
+
+ sink (q);
+ }
+}
+
+
+void warn_fdopen (void)
+{
+ {
+ FILE *q = fdopen (0); // { dg-message "allocated here" }
+ release (q); // { dg-warning "'release' called on 'q' returned from a mismatched allocation function" }
+ }
+ {
+ FILE *q = fdopen (0); // { dg-message "allocated here" }
+ free (q); // { dg-warning "'free' called on 'q' returned from a mismatched allocation function" }
+ }
+
+ {
+ FILE *q = fdopen (0); // { dg-message "allocated here" }
+ q = realloc (q, 7); // { dg-warning "'realloc' called on 'q' returned from a mismatched allocation function" }
+ sink (q);
+ }
+}
+
+
+void nowarn_fopen (void)
+{
+ {
+ FILE *q = fopen ("1", "r");
+ sink (q);
+ fclose (q);
+ }
+
+ {
+ FILE *q = fopen ("2", "r");
+ sink (q);
+ q = freopen ("3", "r", q);
+ sink (q);
+ fclose (q);
+ }
+
+ {
+ FILE *q = fopen ("4", "r");
+ sink (q);
+ }
+}
+
+
+void warn_fopen (void)
+{
+ {
+ FILE *q = fopen ("1", "r");
+ release (q); // { dg-warning "'release' called on 'q' returned from a mismatched allocation function" }
+ fclose (q);
+ }
+ {
+ FILE *q = fdopen (0);
+ free (q); // { dg-warning "'free' called on 'q' returned from a mismatched allocation function" }
+ }
+
+ {
+ FILE *q = fdopen (0);
+ q = realloc (q, 7); // { dg-warning "'realloc' called on 'q' returned from a mismatched allocation function" }
+ sink (q);
+ }
+}
+
+
+void test_popen (void)
+{
+ {
+ FILE *p = popen ("1", "r");
+ sink (p);
+ pclose (p);
+ }
+
+ {
+ FILE *p;
+ p = popen ("2", "r"); // { dg-message "allocated here" }
+ fclose (p); // { dg-warning "'fclose' called on 'p' returned from a mismatched allocation function" }
+ }
+
+ {
+ /* freopen() can close a stream open by popen() but pclose() can't
+ close the stream returned from freopen(). */
+ FILE *p = popen ("2", "r");
+ p = freopen ("3", "r", p); // { dg-message "allocated here" }
+ pclose (p); // { dg-warning "'pclose' called on 'p' returned from a mismatched allocation function" }
+ }
+}
+
+
+void test_tmpfile (void)
+{
+ {
+ FILE *p = tmpfile ();
+ fclose (p);
+ }
+
+ {
+ FILE *p = tmpfile ();
+ p = freopen ("1", "r", p);
+ fclose (p);
+ }
+
+ {
+ FILE *p = tmpfile (); // { dg-message "allocated here" }
+ pclose (p); // { dg-warning "'pclose' called on 'p' returned from a mismatched allocation function" }
+ }
+}
+
+
+void warn_malloc (void)
+{
+ {
+ FILE *p = malloc (100); // { dg-message "allocated here" }
+ fclose (p); // { dg-warning "'p' should have been deallocated with 'free' but was deallocated with 'fclose'" }
+ }
+
+ {
+ FILE *p = malloc (100); // { dg-message "allocated here" }
+ p = freopen ("1", "r", p);// { dg-warning "'p' should have been deallocated with 'free' but was deallocated with 'freopen'" }
+ fclose (p);
+ }
+
+ {
+ FILE *p = malloc (100); // { dg-message "allocated here" }
+ pclose (p); // { dg-warning "'p' should have been deallocated with 'free' but was deallocated with 'pclose'" }
+ }
+}
+
+
+void test_acquire (void)
+{
+ {
+ FILE *p = acquire ();
+ release (p);
+ }
+
+ {
+ FILE *p = acquire ();
+ release (p);
+ }
+
+ {
+ FILE *p = acquire (); // { dg-message "allocated here \\(expects deallocation with 'release'\\)" }
+ fclose (p); // { dg-warning "'p' should have been deallocated with 'release' but was deallocated with 'fclose'" }
+ }
+
+ {
+ FILE *p = acquire (); // { dg-message "allocated here \\(expects deallocation with 'release'\\)" }
+ pclose (p); // { dg-warning "'p' should have been deallocated with 'release' but was deallocated with 'pclose'" }
+ }
+
+ {
+ FILE *p = acquire (); // { dg-message "allocated here \\(expects deallocation with 'release'\\)" }
+ p = freopen ("1", "r", p); // { dg-warning "'p' should have been deallocated with 'release' but was deallocated with 'freopen'" }
+ sink (p);
+ }
+
+ {
+ FILE *p = acquire (); // { dg-message "allocated here \\(expects deallocation with 'release'\\)" }
+ free (p); // { dg-warning "'p' should have been deallocated with 'release' but was deallocated with 'free'" }
+ }
+
+ {
+ FILE *p = acquire (); // { dg-message "allocated here \\(expects deallocation with 'release'\\)" }
+ p = realloc (p, 123); // { dg-warning "'p' should have been deallocated with 'release' but was deallocated with 'realloc'" }
+ sink (p);
+ }
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/attr-malloc-CVE-2019-19078-usb-leak.c b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-CVE-2019-19078-usb-leak.c
new file mode 100644
index 0000000..905d50e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-CVE-2019-19078-usb-leak.c
@@ -0,0 +1,224 @@
+/* Adapted from linux 5.3.11: drivers/net/wireless/ath/ath10k/usb.c
+ Reduced reproducer for CVE-2019-19078 (leak of struct urb). */
+
+typedef unsigned char u8;
+typedef unsigned short u16;
+typedef _Bool bool;
+
+#define ENOMEM 12
+#define EINVAL 22
+
+/* The original file has this licence header. */
+
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2007-2011 Atheros Communications Inc.
+ * Copyright (c) 2011-2012,2017 Qualcomm Atheros, Inc.
+ * Copyright (c) 2016-2017 Erik Stromdahl <erik.stromdahl@gmail.com>
+ */
+
+/* Adapted from include/linux/compiler_attributes.h. */
+#define __aligned(x) __attribute__((__aligned__(x)))
+#define __printf(a, b) __attribute__((__format__(printf, a, b)))
+
+/* Possible macro for the new attribute. */
+#define __malloc(f) __attribute__((malloc(f)));
+
+/* From include/linux/types.h. */
+
+typedef unsigned int gfp_t;
+
+/* Not the real value, which is in include/linux/gfp.h. */
+#define GFP_ATOMIC 32
+
+/* From include/linux/usb.h. */
+
+struct urb;
+extern void usb_free_urb(struct urb *urb);
+extern struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags)
+ __malloc(usb_free_urb);
+/* attribute added as part of testcase */
+
+extern int usb_submit_urb(/*struct urb *urb, */gfp_t mem_flags);
+extern void usb_unanchor_urb(struct urb *urb);
+
+/* From drivers/net/wireless/ath/ath10k/core.h. */
+
+struct ath10k;
+
+struct ath10k {
+ /* [...many other fields removed...] */
+
+ /* must be last */
+ u8 drv_priv[0] __aligned(sizeof(void *));
+};
+
+/* From drivers/net/wireless/ath/ath10k/debug.h. */
+
+enum ath10k_debug_mask {
+ /* [...other values removed...] */
+ ATH10K_DBG_USB_BULK = 0x00080000,
+};
+
+extern unsigned int ath10k_debug_mask;
+
+__printf(3, 4) void __ath10k_dbg(struct ath10k *ar,
+ enum ath10k_debug_mask mask,
+ const char *fmt, ...);
+
+/* Simplified for now, to avoid pulling in tracepoint code. */
+static inline
+bool trace_ath10k_log_dbg_enabled(void) { return 0; }
+
+#define ath10k_dbg(ar, dbg_mask, fmt, ...) \
+do { \
+ if ((ath10k_debug_mask & dbg_mask) || \
+ trace_ath10k_log_dbg_enabled()) \
+ __ath10k_dbg(ar, dbg_mask, fmt, ##__VA_ARGS__); \
+} while (0)
+
+/* From drivers/net/wireless/ath/ath10k/hif.h. */
+
+struct ath10k_hif_sg_item {
+ /* [...other fields removed...] */
+ void *transfer_context; /* NULL = tx completion callback not called */
+};
+
+struct ath10k_hif_ops {
+ /* send a scatter-gather list to the target */
+ int (*tx_sg)(struct ath10k *ar, u8 pipe_id,
+ struct ath10k_hif_sg_item *items, int n_items);
+ /* [...other fields removed...] */
+};
+
+/* From drivers/net/wireless/ath/ath10k/usb.h. */
+
+/* tx/rx pipes for usb */
+enum ath10k_usb_pipe_id {
+ /* [...other values removed...] */
+ ATH10K_USB_PIPE_MAX = 8
+};
+
+struct ath10k_usb_pipe {
+ /* [...all fields removed...] */
+};
+
+/* usb device object */
+struct ath10k_usb {
+ /* [...other fields removed...] */
+ struct ath10k_usb_pipe pipes[ATH10K_USB_PIPE_MAX];
+};
+
+/* usb urb object */
+struct ath10k_urb_context {
+ /* [...other fields removed...] */
+ struct ath10k_usb_pipe *pipe;
+ struct sk_buff *skb;
+};
+
+static inline struct ath10k_usb *ath10k_usb_priv(struct ath10k *ar)
+{
+ return (struct ath10k_usb *)ar->drv_priv;
+}
+
+/* The source file. */
+
+static void ath10k_usb_post_recv_transfers(struct ath10k *ar,
+ struct ath10k_usb_pipe *recv_pipe);
+
+struct ath10k_urb_context *
+ath10k_usb_alloc_urb_from_pipe(struct ath10k_usb_pipe *pipe);
+
+void ath10k_usb_free_urb_to_pipe(struct ath10k_usb_pipe *pipe,
+ struct ath10k_urb_context *urb_context);
+
+static int ath10k_usb_hif_tx_sg(struct ath10k *ar, u8 pipe_id,
+ struct ath10k_hif_sg_item *items, int n_items)
+{
+ struct ath10k_usb *ar_usb = ath10k_usb_priv(ar);
+ struct ath10k_usb_pipe *pipe = &ar_usb->pipes[pipe_id];
+ struct ath10k_urb_context *urb_context;
+ struct sk_buff *skb;
+ struct urb *urb;
+ int ret, i;
+
+ for (i = 0; i < n_items; i++) {
+ urb_context = ath10k_usb_alloc_urb_from_pipe(pipe);
+ if (!urb_context) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ skb = items[i].transfer_context;
+ urb_context->skb = skb;
+
+ urb = usb_alloc_urb(0, GFP_ATOMIC); /* { dg-message "allocated here" } */
+ if (!urb) {
+ ret = -ENOMEM;
+ goto err_free_urb_to_pipe;
+ }
+
+ /* TODO: these are disabled, otherwise we conservatively
+ assume that they could free urb. */
+#if 0
+ usb_fill_bulk_urb(urb,
+ ar_usb->udev,
+ pipe->usb_pipe_handle,
+ skb->data,
+ skb->len,
+ ath10k_usb_transmit_complete, urb_context);
+ if (!(skb->len % pipe->max_packet_size)) {
+ /* hit a max packet boundary on this pipe */
+ urb->transfer_flags |= URB_ZERO_PACKET;
+ }
+
+ usb_anchor_urb(urb, &pipe->urb_submitted);
+#endif
+ /* TODO: initial argument disabled, otherwise we conservatively
+ assume that it could free urb. */
+ ret = usb_submit_urb(/*urb, */GFP_ATOMIC);
+ if (ret) { /* TODO: why doesn't it show this condition at default verbosity? */
+ ath10k_dbg(ar, ATH10K_DBG_USB_BULK,
+ "usb bulk transmit failed: %d\n", ret);
+
+ /* TODO: this is disabled, otherwise we conservatively
+ assume that it could free urb. */
+#if 0
+ usb_unanchor_urb(urb);
+#endif
+
+ ret = -EINVAL;
+ /* Leak of urb happens here. */
+ goto err_free_urb_to_pipe;
+ }
+
+ /* TODO: the loop confuses the double-free checker (another
+ instance of PR analyzer/93695). */
+ usb_free_urb(urb); /* { dg-bogus "double-'usb_free_urb' of 'urb'" "" { xfail *-*-* } } */
+ }
+
+ return 0;
+
+err_free_urb_to_pipe:
+ ath10k_usb_free_urb_to_pipe(urb_context->pipe, urb_context);
+err:
+ return ret; /* { dg-warning "leak of 'urb'" } */
+}
+
+static const struct ath10k_hif_ops ath10k_usb_hif_ops = {
+ .tx_sg = ath10k_usb_hif_tx_sg,
+};
+
+/* Simulate code to register the callback. */
+extern void callback_registration (const void *);
+int ath10k_usb_probe(void)
+{
+ callback_registration(&ath10k_usb_hif_ops);
+}
+
+
+/* The original source file ends with:
+MODULE_AUTHOR("Atheros Communications, Inc.");
+MODULE_DESCRIPTION("Driver support for Qualcomm Atheros 802.11ac WLAN USB devices");
+MODULE_LICENSE("Dual BSD/GPL");
+*/
diff --git a/gcc/testsuite/gcc.dg/analyzer/attr-malloc-misuses.c b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-misuses.c
new file mode 100644
index 0000000..3c6c17b
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/attr-malloc-misuses.c
@@ -0,0 +1,18 @@
+extern void free (void *);
+
+int not_a_fn __attribute__ ((malloc (free))); /* { dg-warning "'malloc' attribute ignored; valid only for functions" } */
+
+void void_return (void) __attribute__ ((malloc(free))); /* { dg-warning "'malloc' attribute ignored on functions returning 'void'" } */
+
+void test_void_return (void)
+{
+ void_return ();
+}
+
+extern void void_args (void); /* { dg-message "declared here" } */
+void *has_malloc_with_void_args (void)
+ __attribute__ ((malloc(void_args))); /* { dg-error "'malloc' attribute argument 1 must take a pointer type as its first argument; have 'void'" } */
+
+extern void no_args (); /* { dg-message "declared here" } */
+void *has_malloc_with_no_args (void)
+ __attribute__ ((malloc(no_args))); /* { dg-error "'malloc' attribute argument 1 must take a pointer type as its first argument" } */