diff options
Diffstat (limited to 'gcc/analyzer/sm-malloc.cc')
-rw-r--r-- | gcc/analyzer/sm-malloc.cc | 767 |
1 files changed, 646 insertions, 121 deletions
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); } } |