Most of the state-management code in the analyzer involves modifying state objects in-place, which implies a single outcome. (I originally implemented in-place modification because I wanted to avoid having to create copies of state objects, and it's now very difficult to change this aspect of the analyzer's design)
However, there are various special-cases such as "realloc" for which it's best to split the state into multiple outcomes. This patch adds a mechanism for "bifurcating" the analysis in places where there isn't a split in the CFG, and uses it to implement realloc, in this case treating it as having 3 possible outcomes: - failure, returning NULL - success, growing the buffer in-place without moving it - success, allocating a new buffer, copying the content of the old buffer to it, and freeing the old buffer. Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu. Pushed to trunk as r12-3237-geafa9d969237fd8f712c4b25a8c58932c01f44b4. gcc/ChangeLog: PR analyzer/99260 * Makefile.in (ANALYZER_OBJS): Add analyzer/call-info.o. gcc/analyzer/ChangeLog: PR analyzer/99260 * analyzer.h (class custom_edge_info): New class, adapted from exploded_edge::custom_info_t. Make member functions const. Make update_model return bool, converting edge param from reference to a pointer, and adding a ctxt param. (class path_context): New class. * call-info.cc: New file. * call-info.h: New file. * engine.cc: Include "analyzer/call-info.h" and <memory>. (impl_region_model_context::impl_region_model_context): Update for new m_path_ctxt field. (impl_region_model_context::bifurcate): New. (impl_region_model_context::terminate_path): New. (impl_region_model_context::get_malloc_map): New. (impl_sm_context::impl_sm_context): Update for new m_path_ctxt field. (impl_sm_context::get_fndecl_for_call): Likewise. (impl_sm_context::set_next_state): Likewise. (impl_sm_context::warn): Likewise. (impl_sm_context::is_zero_assignment): Likewise. (impl_sm_context::get_path_context): New. (impl_sm_context::m_path_ctxt): New. (impl_region_model_context::on_condition): Update for new path_ctxt param. Handle m_enode_for_diag being NULL. (impl_region_model_context::on_phi): Update for new path_ctxt param. (exploded_node::on_stmt): Add path_ctxt param, updating ctor calls to use it as necessary. Use it to bail out after sm-handling, if needed. (exploded_node::detect_leaks): Update for new path_ctxt param. (dynamic_call_info_t::update_model): Update for conversion of exploded_edge::custom_info_t to custom_edge_info. (dynamic_call_info_t::add_events_to_path): Likewise. (rewind_info_t::update_model): Likewise. (rewind_info_t::add_events_to_path): Likewise. (exploded_edge::exploded_edge): Likewise. (exploded_graph::add_edge): Likewise. (exploded_graph::maybe_process_run_of_before_supernode_enodes): Update for new path_ctxt param. (class impl_path_context): New. (exploded_graph::process_node): Update for new path_ctxt param. Create an impl_path_context and pass it to exploded_node::on_stmt. Use it to terminate iterating stmts if terminate_path is called on it. After processing a run of stmts, query path_ctxt to potentially terminate the analysis path, and/or to "bifurcate" the analysis into multiple additional paths. (feasibility_state::maybe_update_for_edge): Update for new update_model ctxt param. * exploded-graph.h (impl_region_model_context::impl_region_model_context): Add path_ctxt param. (impl_region_model_context::bifurcate): New. (impl_region_model_context::terminate_path): New (impl_region_model_context::get_ext_state): New. (impl_region_model_context::get_malloc_map): New. (impl_region_model_context::m_path_ctxt): New field. (exploded_node::on_stmt): Add path_ctxt param. (class exploded_edge::custom_info_t): Move to analyzer.h, renaming to custom_edge_info, and making the changes as noted in analyzer.h above. (exploded_edge::exploded_edge): Update for these changes to exploded_edge::custom_info_t. (exploded_edge::m_custom_info): Likewise. (class dynamic_call_info_t): Likewise. (class rewind_info_t): Likewise. (exploded_graph::add_edge): Likewise. * program-state.cc (program_state::on_edge): Update for new path_ctxt param. (program_state::push_call): Likewise. (program_state::returning_call): Likewise. (program_state::prune_for_point): Likewise. * region-model-impl-calls.cc: Include "analyzer/call-info.h". (call_details::get_fndecl_for_call): New. (region_model::impl_call_realloc): Reimplement. * region-model.cc (region_model::on_call_pre): Move call to impl_call_realloc to... (region_model::on_call_post): ...here. Consolidate creation of call_details instance. (noop_region_model_context::bifurcate): New. (noop_region_model_context::terminate_path): New. * region-model.h (call_details::get_call_stmt): New. (call_details::get_fndecl_for_call): New. (region_model::on_realloc_with_move): New. (region_model_context::bifurcate): New. (region_model_context::terminate_path): New. (region_model_context::get_ext_state): New. (region_model_context::get_malloc_map): New. (noop_region_model_context::bifurcate): New. (noop_region_model_context::terminate_path): New. (noop_region_model_context::get_ext_state): New. (noop_region_model_context::get_malloc_map): New. * sm-malloc.cc: Include "analyzer/program-state.h". (malloc_state_machine::on_realloc_call): Reimplement. (malloc_state_machine::on_realloc_with_move): New. (region_model::on_realloc_with_move): New. * sm-signal.cc (class signal_delivery_edge_info_t): Update for conversion from exploded_edge::custom_info_t to custom_edge_info. * sm.h (sm_context::get_path_context): New. * svalue.cc (svalue::maybe_get_constant): Call unwrap_any_unmergeable. gcc/testsuite/ChangeLog: PR analyzer/99260 * gcc.dg/analyzer/capacity-2.c: Update for changes to realloc analysis. * gcc.dg/analyzer/pr99193-1.c: Likewise. * gcc.dg/analyzer/pr99193-3.c: Likewise. * gcc.dg/analyzer/realloc-1.c: Likewise. Add test coverage for realloc of non-heap pointer, realloc from mismatching allocator, and realloc on a freed pointer. * gcc.dg/analyzer/realloc-2.c: New test. --- gcc/Makefile.in | 1 + gcc/analyzer/analyzer.h | 51 ++++ gcc/analyzer/call-info.cc | 162 ++++++++++++ gcc/analyzer/call-info.h | 83 +++++++ gcc/analyzer/engine.cc | 271 +++++++++++++++++---- gcc/analyzer/exploded-graph.h | 62 +++-- gcc/analyzer/program-state.cc | 6 +- gcc/analyzer/region-model-impl-calls.cc | 176 ++++++++++++- gcc/analyzer/region-model.cc | 28 ++- gcc/analyzer/region-model.h | 36 +++ gcc/analyzer/sm-malloc.cc | 136 ++++++++--- gcc/analyzer/sm-signal.cc | 15 +- gcc/analyzer/sm.h | 5 + gcc/analyzer/svalue.cc | 3 +- gcc/testsuite/gcc.dg/analyzer/capacity-2.c | 8 +- gcc/testsuite/gcc.dg/analyzer/pr99193-1.c | 2 + gcc/testsuite/gcc.dg/analyzer/pr99193-3.c | 2 + gcc/testsuite/gcc.dg/analyzer/realloc-1.c | 47 +++- gcc/testsuite/gcc.dg/analyzer/realloc-2.c | 80 ++++++ 19 files changed, 1042 insertions(+), 132 deletions(-) create mode 100644 gcc/analyzer/call-info.cc create mode 100644 gcc/analyzer/call-info.h create mode 100644 gcc/testsuite/gcc.dg/analyzer/realloc-2.c diff --git a/gcc/Makefile.in b/gcc/Makefile.in index 9714fcaac37..f0c560fe45b 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -1249,6 +1249,7 @@ ANALYZER_OBJS = \ analyzer/analyzer-pass.o \ analyzer/analyzer-selftests.o \ analyzer/bar-chart.o \ + analyzer/call-info.o \ analyzer/call-string.o \ analyzer/checker-path.o \ analyzer/complexity.o \ diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h index 05d47512bd7..7ad1081ca6c 100644 --- a/gcc/analyzer/analyzer.h +++ b/gcc/analyzer/analyzer.h @@ -220,6 +220,57 @@ enum access_direction DIR_WRITE }; +/* Abstract base class for associating custom data with an + exploded_edge, for handling non-standard edges such as + rewinding from a longjmp, signal handlers, etc. + Also used when "bifurcating" state: splitting the execution + path in non-standard ways (e.g. for simulating the various + outcomes of "realloc"). */ + +class custom_edge_info +{ +public: + virtual ~custom_edge_info () {} + + /* Hook for making .dot label more readable. */ + virtual void print (pretty_printer *pp) const = 0; + + /* Hook for updating MODEL within exploded_path::feasible_p + and when handling bifurcation. */ + virtual bool update_model (region_model *model, + const exploded_edge *eedge, + region_model_context *ctxt) const = 0; + + virtual void add_events_to_path (checker_path *emission_path, + const exploded_edge &eedge) const = 0; +}; + +/* Abstract base class for splitting state. + + Most of the state-management code in the analyzer involves + modifying state objects in-place, which assumes a single outcome. + + This class provides an escape hatch to allow for multiple outcomes + for such updates e.g. for modelling multiple outcomes from function + calls, such as the various outcomes of "realloc". */ + +class path_context +{ +public: + virtual ~path_context () {} + + /* Hook for clients to split state with a non-standard path. + Take ownership of INFO. */ + virtual void bifurcate (custom_edge_info *info) = 0; + + /* Hook for clients to terminate the standard path. */ + virtual void terminate_path () = 0; + + /* Hook for clients to determine if the standard path has been + terminated. */ + virtual bool terminate_path_p () const = 0; +}; + } // namespace ana extern bool is_special_named_call_p (const gcall *call, const char *funcname, diff --git a/gcc/analyzer/call-info.cc b/gcc/analyzer/call-info.cc new file mode 100644 index 00000000000..1d44cb88221 --- /dev/null +++ b/gcc/analyzer/call-info.cc @@ -0,0 +1,162 @@ +/* Subclasses of custom_edge_info for describing outcomes of function calls. + Copyright (C) 2021 Free Software Foundation, Inc. + Contributed by David Malcolm <dmalc...@redhat.com>. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "tree.h" +#include "function.h" +#include "basic-block.h" +#include "gimple.h" +#include "gimple-iterator.h" +#include "diagnostic-core.h" +#include "options.h" +#include "cgraph.h" +#include "tree-pretty-print.h" +#include "tristate.h" +#include "bitmap.h" +#include "selftest.h" +#include "function.h" +#include "json.h" +#include "analyzer/analyzer.h" +#include "analyzer/analyzer-logging.h" +#include "ordered-hash-map.h" +#include "cfg.h" +#include "digraph.h" +#include "analyzer/supergraph.h" +#include "sbitmap.h" +#include "analyzer/call-string.h" +#include "analyzer/program-point.h" +#include "analyzer/store.h" +#include "analyzer/region-model.h" +#include "analyzer/constraint-manager.h" +#include "diagnostic-event-id.h" +#include "analyzer/sm.h" +#include "analyzer/pending-diagnostic.h" +#include "analyzer/region-model-reachability.h" +#include "analyzer/analyzer-selftests.h" +#include "analyzer/program-state.h" +#include "diagnostic-path.h" +#include "analyzer/checker-path.h" +#include "analyzer/diagnostic-manager.h" +#include "alloc-pool.h" +#include "fibonacci_heap.h" +#include "shortest-paths.h" +#include "analyzer/exploded-graph.h" +#include "analyzer/call-info.h" + +#if ENABLE_ANALYZER + +namespace ana { + +/* class call_info : public custom_eedge_info_t. */ + +/* Implementation of custom_edge_info::print vfunc for call_info: + use get_desc to get a label_text, and print it to PP. */ + +void +call_info::print (pretty_printer *pp) const +{ + label_text desc (get_desc (pp_show_color (pp))); + pp_string (pp, desc.m_buffer); + desc.maybe_free (); +} + +/* Implementation of custom_edge_info::add_events_to_path vfunc for + call_info: add a custom_event using call_info::get_desc as its + description. */ + +void +call_info::add_events_to_path (checker_path *emission_path, + const exploded_edge &eedge) const +{ + class call_event : public custom_event + { + public: + call_event (location_t loc, tree fndecl, int depth, + const call_info *call_info) + : custom_event (loc, fndecl, depth), + m_call_info (call_info) + {} + + label_text get_desc (bool can_colorize) const + { + return m_call_info->get_desc (can_colorize); + } + + private: + const call_info *m_call_info; + }; + + const exploded_node *src_node = eedge.m_src; + const program_point &src_point = src_node->get_point (); + tree caller_fndecl = src_point.get_fndecl (); + const int stack_depth = src_point.get_stack_depth (); + + emission_path->add_event (new call_event (get_call_stmt ()->location, + caller_fndecl, + stack_depth, + this)); +} + +/* Recreate a call_details instance from this call_info. */ + +call_details +call_info::get_call_details (region_model *model, + region_model_context *ctxt) const +{ + return call_details (m_call_stmt, model, ctxt); +} + +/* call_info's ctor. + + The call_info instance will outlive the call_details instance; + call_details instances are typically created on the stack. */ + +call_info::call_info (const call_details &cd) +: m_call_stmt (cd.get_call_stmt ()), + m_fndecl (cd.get_fndecl_for_call ()) +{ + gcc_assert (m_fndecl); +} + +/* class success_call_info : public call_info. */ + +/* Implementation of call_info::get_desc vfunc for success_call_info. */ + +label_text +success_call_info::get_desc (bool can_colorize) const +{ + return make_label_text (can_colorize, "when %qE succeeds", get_fndecl ()); +} + +/* class failed_call_info : public call_info. */ + +/* Implementation of call_info::get_desc vfunc for failed_call_info. */ + +label_text +failed_call_info::get_desc (bool can_colorize) const +{ + return make_label_text (can_colorize, "when %qE fails", get_fndecl ()); +} + +} // namespace ana + +#endif /* #if ENABLE_ANALYZER */ diff --git a/gcc/analyzer/call-info.h b/gcc/analyzer/call-info.h new file mode 100644 index 00000000000..369d217a22f --- /dev/null +++ b/gcc/analyzer/call-info.h @@ -0,0 +1,83 @@ +/* Subclasses of custom_edge_info for describing outcomes of function calls. + Copyright (C) 2021 Free Software Foundation, Inc. + Contributed by David Malcolm <dmalc...@redhat.com>. + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#ifndef GCC_ANALYZER_CALL_INFO_H +#define GCC_ANALYZER_CALL_INFO_H + +namespace ana { + +/* Subclass of custom_edge_info for an outcome of a call. + This is still abstract; the update_model and get_desc vfuncs must be + implemented. */ + +class call_info : public custom_edge_info +{ +public: + void print (pretty_printer *pp) const FINAL OVERRIDE; + void add_events_to_path (checker_path *emission_path, + const exploded_edge &eedge) const FINAL OVERRIDE; + + const gcall *get_call_stmt () const { return m_call_stmt; } + tree get_fndecl () const { return m_fndecl; } + + virtual label_text get_desc (bool can_colorize) const = 0; + + call_details get_call_details (region_model *model, + region_model_context *ctxt) const; + +protected: + call_info (const call_details &cd); + +private: + const gcall *m_call_stmt; + tree m_fndecl; +}; + +/* Subclass of call_info for a "success" outcome of a call, + adding a "when `FNDECL' succeeds" message. + This is still abstract: the custom_edge_info::update_model vfunc + must be implemented. */ + +class success_call_info : public call_info +{ +public: + label_text get_desc (bool can_colorize) const FINAL OVERRIDE; + +protected: + success_call_info (const call_details &cd) : call_info (cd) {} +}; + +/* Subclass of call_info for a "failure" outcome of a call, + adding a "when `FNDECL' fails" message. + This is still abstract: the custom_edge_info::update_model vfunc + must be implemented. */ + +class failed_call_info : public call_info +{ +public: + label_text get_desc (bool can_colorize) const FINAL OVERRIDE; + +protected: + failed_call_info (const call_details &cd) : call_info (cd) {} +}; + +} // namespace ana + +#endif /* GCC_ANALYZER_CALL_INFO_H */ diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index 9c604d1eb8c..24f0931197d 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -62,9 +62,11 @@ along with GCC; see the file COPYING3. If not see #include "analyzer/checker-path.h" #include "analyzer/state-purge.h" #include "analyzer/bar-chart.h" +#include "analyzer/call-info.h" #include <zlib.h> #include "plugin.h" #include "target.h" +#include <memory> /* For an overview, see gcc/doc/analyzer.texi. */ @@ -80,6 +82,7 @@ impl_region_model_context (exploded_graph &eg, const program_state *old_state, program_state *new_state, uncertainty_t *uncertainty, + path_context *path_ctxt, const gimple *stmt, stmt_finder *stmt_finder) : m_eg (&eg), m_logger (eg.get_logger ()), @@ -89,7 +92,8 @@ impl_region_model_context (exploded_graph &eg, m_stmt (stmt), m_stmt_finder (stmt_finder), m_ext_state (eg.get_ext_state ()), - m_uncertainty (uncertainty) + m_uncertainty (uncertainty), + m_path_ctxt (path_ctxt) { } @@ -104,7 +108,8 @@ impl_region_model_context (program_state *state, m_stmt (NULL), m_stmt_finder (NULL), m_ext_state (ext_state), - m_uncertainty (uncertainty) + m_uncertainty (uncertainty), + m_path_ctxt (NULL) { } @@ -183,6 +188,37 @@ impl_region_model_context::purge_state_involving (const svalue *sval) smap->purge_state_involving (sval, m_ext_state); } +void +impl_region_model_context::bifurcate (custom_edge_info *info) +{ + if (m_path_ctxt) + m_path_ctxt->bifurcate (info); + else + delete info; +} + +void +impl_region_model_context::terminate_path () +{ + if (m_path_ctxt) + return m_path_ctxt->terminate_path (); +} + +bool +impl_region_model_context::get_malloc_map (sm_state_map **out_smap, + const state_machine **out_sm, + unsigned *out_sm_idx) +{ + unsigned malloc_sm_idx; + if (!m_ext_state.get_sm_idx_by_name ("malloc", &malloc_sm_idx)) + return false; + + *out_smap = m_new_state->m_checker_states[malloc_sm_idx]; + *out_sm = &m_ext_state.get_sm (malloc_sm_idx); + *out_sm_idx = malloc_sm_idx; + return true; +} + /* struct setjmp_record. */ int @@ -237,12 +273,14 @@ public: program_state *new_state, const sm_state_map *old_smap, sm_state_map *new_smap, + path_context *path_ctxt, stmt_finder *stmt_finder = NULL) : sm_context (sm_idx, sm), m_logger (eg.get_logger ()), m_eg (eg), m_enode_for_diag (enode_for_diag), m_old_state (old_state), m_new_state (new_state), m_old_smap (old_smap), m_new_smap (new_smap), + m_path_ctxt (path_ctxt), m_stmt_finder (stmt_finder) { } @@ -252,7 +290,7 @@ public: tree get_fndecl_for_call (const gcall *call) FINAL OVERRIDE { impl_region_model_context old_ctxt - (m_eg, m_enode_for_diag, NULL, NULL/*m_enode->get_state ()*/, + (m_eg, m_enode_for_diag, NULL, NULL, NULL/*m_enode->get_state ()*/, NULL, call); region_model *model = m_new_state->m_region_model; return model->get_fndecl_for_call (call, &old_ctxt); @@ -292,7 +330,7 @@ public: LOG_FUNC (logger); impl_region_model_context new_ctxt (m_eg, m_enode_for_diag, m_old_state, m_new_state, - NULL, + NULL, NULL, stmt); const svalue *var_new_sval = m_new_state->m_region_model->get_rvalue (var, &new_ctxt); @@ -320,12 +358,12 @@ public: logger * const logger = get_logger (); LOG_FUNC (logger); impl_region_model_context old_ctxt - (m_eg, m_enode_for_diag, NULL, NULL/*m_enode->get_state ()*/, + (m_eg, m_enode_for_diag, NULL, NULL, NULL/*m_enode->get_state ()*/, NULL, stmt); impl_region_model_context new_ctxt (m_eg, m_enode_for_diag, m_old_state, m_new_state, - NULL, + NULL, NULL, stmt); const svalue *origin_new_sval = m_new_state->m_region_model->get_rvalue (origin, &new_ctxt); @@ -353,7 +391,7 @@ public: LOG_FUNC (get_logger ()); gcc_assert (d); // take ownership impl_region_model_context old_ctxt - (m_eg, m_enode_for_diag, m_old_state, m_new_state, NULL, NULL); + (m_eg, m_enode_for_diag, m_old_state, m_new_state, NULL, NULL, NULL); const svalue *var_old_sval = m_old_state->m_region_model->get_rvalue (var, &old_ctxt); @@ -418,7 +456,7 @@ public: if (!assign_stmt) return NULL_TREE; impl_region_model_context old_ctxt - (m_eg, m_enode_for_diag, m_old_state, m_new_state, NULL, stmt); + (m_eg, m_enode_for_diag, m_old_state, m_new_state, NULL, NULL, stmt); if (const svalue *sval = m_new_state->m_region_model->get_gassign_result (assign_stmt, &old_ctxt)) @@ -428,6 +466,11 @@ public: return NULL_TREE; } + path_context *get_path_context () const FINAL OVERRIDE + { + return m_path_ctxt; + } + log_user m_logger; exploded_graph &m_eg; exploded_node *m_enode_for_diag; @@ -435,6 +478,7 @@ public: program_state *m_new_state; const sm_state_map *m_old_smap; sm_state_map *m_new_smap; + path_context *m_path_ctxt; stmt_finder *m_stmt_finder; }; @@ -751,9 +795,13 @@ impl_region_model_context::on_condition (const svalue *lhs, impl_sm_context sm_ctxt (*m_eg, sm_idx, sm, m_enode_for_diag, m_old_state, m_new_state, m_old_state->m_checker_states[sm_idx], - m_new_state->m_checker_states[sm_idx]); + m_new_state->m_checker_states[sm_idx], + m_path_ctxt); sm.on_condition (&sm_ctxt, - m_enode_for_diag->get_supernode (), m_stmt, + (m_enode_for_diag + ? m_enode_for_diag->get_supernode () + : NULL), + m_stmt, lhs, op, rhs); } } @@ -773,7 +821,8 @@ impl_region_model_context::on_phi (const gphi *phi, tree rhs) impl_sm_context sm_ctxt (*m_eg, sm_idx, sm, m_enode_for_diag, m_old_state, m_new_state, m_old_state->m_checker_states[sm_idx], - m_new_state->m_checker_states[sm_idx]); + m_new_state->m_checker_states[sm_idx], + m_path_ctxt); sm.on_phi (&sm_ctxt, m_enode_for_diag->get_supernode (), phi, rhs); } } @@ -1190,7 +1239,8 @@ exploded_node::on_stmt (exploded_graph &eg, const supernode *snode, const gimple *stmt, program_state *state, - uncertainty_t *uncertainty) + uncertainty_t *uncertainty, + path_context *path_ctxt) { logger *logger = eg.get_logger (); LOG_SCOPE (logger); @@ -1215,7 +1265,7 @@ exploded_node::on_stmt (exploded_graph &eg, impl_region_model_context ctxt (eg, this, &old_state, state, uncertainty, - stmt); + path_ctxt, stmt); bool unknown_side_effects = false; bool terminate_path = false; @@ -1235,13 +1285,16 @@ exploded_node::on_stmt (exploded_graph &eg, = old_state.m_checker_states[sm_idx]; sm_state_map *new_smap = state->m_checker_states[sm_idx]; impl_sm_context sm_ctxt (eg, sm_idx, sm, this, &old_state, state, - old_smap, new_smap); + old_smap, new_smap, path_ctxt); /* Allow the state_machine to handle the stmt. */ if (sm.on_stmt (&sm_ctxt, snode, stmt)) unknown_side_effects = false; } + if (path_ctxt->terminate_path_p ()) + return on_stmt_flags::terminate_path (); + on_stmt_post (stmt, state, unknown_side_effects, &ctxt); return on_stmt_flags (); @@ -1592,7 +1645,7 @@ exploded_node::detect_leaks (exploded_graph &eg) uncertainty_t uncertainty; impl_region_model_context ctxt (eg, this, - &old_state, &new_state, &uncertainty, + &old_state, &new_state, &uncertainty, NULL, get_stmt ()); const svalue *result = NULL; new_state.m_region_model->pop_frame (NULL, &result, &ctxt); @@ -1627,27 +1680,30 @@ exploded_node::dump_succs_and_preds (FILE *outf) const } } -/* class dynamic_call_info_t : public exploded_edge::custom_info_t. */ +/* class dynamic_call_info_t : public custom_edge_info. */ -/* Implementation of exploded_edge::custom_info_t::update_model vfunc +/* Implementation of custom_edge_info::update_model vfunc for dynamic_call_info_t. Update state for the dynamically discorverd calls */ -void +bool dynamic_call_info_t::update_model (region_model *model, - const exploded_edge &eedge) + const exploded_edge *eedge, + region_model_context *) const { - const program_state &dest_state = eedge.m_dest->get_state (); + gcc_assert (eedge); + const program_state &dest_state = eedge->m_dest->get_state (); *model = *dest_state.m_region_model; + return true; } -/* Implementation of exploded_edge::custom_info_t::add_events_to_path vfunc +/* Implementation of custom_edge_info::add_events_to_path vfunc for dynamic_call_info_t. */ void dynamic_call_info_t::add_events_to_path (checker_path *emission_path, - const exploded_edge &eedge) + const exploded_edge &eedge) const { const exploded_node *src_node = eedge.m_src; const program_point &src_point = src_node->get_point (); @@ -1671,21 +1727,23 @@ dynamic_call_info_t::add_events_to_path (checker_path *emission_path, } -/* class rewind_info_t : public exploded_edge::custom_info_t. */ +/* class rewind_info_t : public custom_edge_info. */ -/* Implementation of exploded_edge::custom_info_t::update_model vfunc +/* Implementation of custom_edge_info::update_model vfunc for rewind_info_t. Update state for the special-case of a rewind of a longjmp to a setjmp (which doesn't have a superedge, but does affect state). */ -void +bool rewind_info_t::update_model (region_model *model, - const exploded_edge &eedge) + const exploded_edge *eedge, + region_model_context *) const { - const program_point &longjmp_point = eedge.m_src->get_point (); - const program_point &setjmp_point = eedge.m_dest->get_point (); + gcc_assert (eedge); + const program_point &longjmp_point = eedge->m_src->get_point (); + const program_point &setjmp_point = eedge->m_dest->get_point (); gcc_assert (longjmp_point.get_stack_depth () >= setjmp_point.get_stack_depth ()); @@ -1693,14 +1751,15 @@ rewind_info_t::update_model (region_model *model, model->on_longjmp (get_longjmp_call (), get_setjmp_call (), setjmp_point.get_stack_depth (), NULL); + return true; } -/* Implementation of exploded_edge::custom_info_t::add_events_to_path vfunc +/* Implementation of custom_edge_info::add_events_to_path vfunc for rewind_info_t. */ void rewind_info_t::add_events_to_path (checker_path *emission_path, - const exploded_edge &eedge) + const exploded_edge &eedge) const { const exploded_node *src_node = eedge.m_src; const program_point &src_point = src_node->get_point (); @@ -1727,7 +1786,7 @@ rewind_info_t::add_events_to_path (checker_path *emission_path, exploded_edge::exploded_edge (exploded_node *src, exploded_node *dest, const superedge *sedge, - custom_info_t *custom_info) + custom_edge_info *custom_info) : dedge<eg_traits> (src, dest), m_sedge (sedge), m_custom_info (custom_info) { @@ -2432,7 +2491,7 @@ exploded_graph::get_or_create_node (const program_point &point, exploded_edge * exploded_graph::add_edge (exploded_node *src, exploded_node *dest, const superedge *sedge, - exploded_edge::custom_info_t *custom_info) + custom_edge_info *custom_info) { if (get_logger ()) get_logger ()->log ("creating edge EN: %i -> EN: %i", @@ -2866,7 +2925,7 @@ maybe_process_run_of_before_supernode_enodes (exploded_node *enode) uncertainty_t uncertainty; impl_region_model_context ctxt (*this, iter_enode, &state, next_state, - &uncertainty, NULL); + &uncertainty, NULL, NULL); const cfg_superedge *last_cfg_superedge = iter_sedge->dyn_cast_cfg_superedge (); if (last_cfg_superedge) @@ -3095,6 +3154,72 @@ exploded_graph::maybe_create_dynamic_call (const gcall *call, return false; } +/* Subclass of path_context for use within exploded_graph::process_node, + so that we can split states e.g. at "realloc" calls. */ + +class impl_path_context : public path_context +{ +public: + impl_path_context (const program_state *cur_state) + : m_cur_state (cur_state), + m_terminate_path (false) + { + } + + bool bifurcation_p () const + { + return m_custom_eedge_infos.length () > 0; + } + + const program_state &get_state_at_bifurcation () const + { + gcc_assert (m_state_at_bifurcation); + return *m_state_at_bifurcation; + } + + void + bifurcate (custom_edge_info *info) FINAL OVERRIDE + { + if (m_state_at_bifurcation) + /* Verify that the state at bifurcation is consistent when we + split into multiple out-edges. */ + gcc_assert (*m_state_at_bifurcation == *m_cur_state); + else + /* Take a copy of the cur_state at the moment when bifurcation + happens. */ + m_state_at_bifurcation + = std::unique_ptr<program_state> (new program_state (*m_cur_state)); + + /* Take ownership of INFO. */ + m_custom_eedge_infos.safe_push (info); + } + + void terminate_path () FINAL OVERRIDE + { + m_terminate_path = true; + } + + bool terminate_path_p () const FINAL OVERRIDE + { + return m_terminate_path; + } + + const vec<custom_edge_info *> & get_custom_eedge_infos () + { + return m_custom_eedge_infos; + } + +private: + const program_state *m_cur_state; + + /* Lazily-created copy of the state before the split. */ + std::unique_ptr<program_state> m_state_at_bifurcation; + + auto_vec <custom_edge_info *> m_custom_eedge_infos; + + bool m_terminate_path; +}; + /* The core of exploded_graph::process_worklist (the main analysis loop), handling one node in the worklist. @@ -3150,7 +3275,7 @@ exploded_graph::process_node (exploded_node *node) { impl_region_model_context ctxt (*this, node, &state, &next_state, - &uncertainty, NULL); + &uncertainty, NULL, NULL); const cfg_superedge *last_cfg_superedge = point.get_from_edge ()->dyn_cast_cfg_superedge (); if (last_cfg_superedge) @@ -3188,6 +3313,9 @@ exploded_graph::process_node (exploded_node *node) the sm-state-change occurs on an edge where the src enode has exactly one stmt, the one that caused the change. */ program_state next_state (state); + + impl_path_context path_ctxt (&next_state); + uncertainty_t uncertainty; const supernode *snode = point.get_supernode (); unsigned stmt_idx; @@ -3210,7 +3338,8 @@ exploded_graph::process_node (exploded_node *node) /* Process the stmt. */ exploded_node::on_stmt_flags flags - = node->on_stmt (*this, snode, stmt, &next_state, &uncertainty); + = node->on_stmt (*this, snode, stmt, &next_state, &uncertainty, + &path_ctxt); node->m_num_processed_stmts++; /* If flags.m_terminate_path, stop analyzing; any nodes/edges @@ -3222,7 +3351,7 @@ exploded_graph::process_node (exploded_node *node) { impl_region_model_context ctxt (*this, node, &old_state, &next_state, - &uncertainty, stmt); + &uncertainty, NULL, stmt); program_state::detect_leaks (old_state, next_state, NULL, get_ext_state (), &ctxt); } @@ -3238,7 +3367,9 @@ exploded_graph::process_node (exploded_node *node) &uncertainty); if (flag_analyzer_fine_grained - || state_change_requires_new_enode_p (old_state, next_state)) + || state_change_requires_new_enode_p (old_state, next_state) + || path_ctxt.bifurcation_p () + || path_ctxt.terminate_path_p ()) { program_point split_point = program_point::before_stmt (point.get_supernode (), @@ -3282,9 +3413,66 @@ exploded_graph::process_node (exploded_node *node) point.get_call_string ()) : program_point::after_supernode (point.get_supernode (), point.get_call_string ())); - exploded_node *next = get_or_create_node (next_point, next_state, node); - if (next) - add_edge (node, next, NULL); + if (path_ctxt.terminate_path_p ()) + { + if (logger) + logger->log ("not adding node: terminating path"); + } + else + { + exploded_node *next + = get_or_create_node (next_point, next_state, node); + if (next) + add_edge (node, next, NULL); + } + + /* If we have custom edge infos, "bifurcate" the state + accordingly, potentially creating a new state/enode/eedge + instances. For example, to handle a "realloc" call, we + might split into 3 states, for the "failure", + "resizing in place", and "moving to a new buffer" cases. */ + for (auto edge_info : path_ctxt.get_custom_eedge_infos ()) + { + if (logger) + { + logger->start_log_line (); + logger->log_partial ("bifurcating for edge: "); + edge_info->print (logger->get_printer ()); + logger->end_log_line (); + } + program_state bifurcated_new_state + (path_ctxt.get_state_at_bifurcation ()); + + /* Apply edge_info to state. */ + impl_region_model_context + bifurcation_ctxt (*this, + NULL, // enode_for_diag + &path_ctxt.get_state_at_bifurcation (), + &bifurcated_new_state, + NULL, // uncertainty_t *uncertainty + NULL, // path_context *path_ctxt + stmt); + if (edge_info->update_model (bifurcated_new_state.m_region_model, + NULL, /* no exploded_edge yet. */ + &bifurcation_ctxt)) + { + exploded_node *next2 + = get_or_create_node (next_point, bifurcated_new_state, node); + if (next2) + { + /* Take ownership of edge_info. */ + add_edge (node, next2, NULL, edge_info); + } + else + delete edge_info; + } + else + { + if (logger) + logger->log ("infeasible state, not adding node"); + delete edge_info; + } + } } break; case PK_AFTER_SUPERNODE: @@ -3351,6 +3539,7 @@ exploded_graph::process_node (exploded_node *node) &state, &next_state, &uncertainty, + NULL, point.get_stmt()); region_model *model = state.m_region_model; @@ -3968,7 +4157,7 @@ feasibility_state::maybe_update_for_edge (logger *logger, } else if (eedge->m_custom_info) { - eedge->m_custom_info->update_model (&m_model, *eedge); + eedge->m_custom_info->update_model (&m_model, eedge, NULL); } } diff --git a/gcc/analyzer/exploded-graph.h b/gcc/analyzer/exploded-graph.h index 6890e84f985..b9c17672aec 100644 --- a/gcc/analyzer/exploded-graph.h +++ b/gcc/analyzer/exploded-graph.h @@ -37,6 +37,7 @@ class impl_region_model_context : public region_model_context const program_state *old_state, program_state *new_state, uncertainty_t *uncertainty, + path_context *path_ctxt, const gimple *stmt, stmt_finder *stmt_finder = NULL); @@ -76,6 +77,16 @@ class impl_region_model_context : public region_model_context void purge_state_involving (const svalue *sval) FINAL OVERRIDE; + void bifurcate (custom_edge_info *info) FINAL OVERRIDE; + void terminate_path () FINAL OVERRIDE; + const extrinsic_state *get_ext_state () const FINAL OVERRIDE + { + return &m_ext_state; + } + bool get_malloc_map (sm_state_map **out_smap, + const state_machine **out_sm, + unsigned *out_sm_idx) FINAL OVERRIDE; + exploded_graph *m_eg; log_user m_logger; exploded_node *m_enode_for_diag; @@ -85,6 +96,7 @@ class impl_region_model_context : public region_model_context stmt_finder *m_stmt_finder; const extrinsic_state &m_ext_state; uncertainty_t *m_uncertainty; + path_context *m_path_ctxt; }; /* A <program_point, program_state> pair, used internally by @@ -224,7 +236,8 @@ class exploded_node : public dnode<eg_traits> const supernode *snode, const gimple *stmt, program_state *state, - uncertainty_t *uncertainty); + uncertainty_t *uncertainty, + path_context *path_ctxt); void on_stmt_pre (exploded_graph &eg, const gimple *stmt, program_state *state, @@ -319,28 +332,9 @@ public: class exploded_edge : public dedge<eg_traits> { public: - /* Abstract base class for associating custom data with an - exploded_edge, for handling non-standard edges such as - rewinding from a longjmp, signal handlers, etc. */ - class custom_info_t - { - public: - virtual ~custom_info_t () {} - - /* Hook for making .dot label more readable . */ - virtual void print (pretty_printer *pp) = 0; - - /* Hook for updating MODEL within exploded_path::feasible_p. */ - virtual void update_model (region_model *model, - const exploded_edge &eedge) = 0; - - virtual void add_events_to_path (checker_path *emission_path, - const exploded_edge &eedge) = 0; - }; - exploded_edge (exploded_node *src, exploded_node *dest, const superedge *sedge, - custom_info_t *custom_info); + custom_edge_info *custom_info); ~exploded_edge (); void dump_dot (graphviz_out *gv, const dump_args_t &args) const FINAL OVERRIDE; @@ -356,7 +350,7 @@ class exploded_edge : public dedge<eg_traits> a signal is delivered to a signal-handler. Owned by this class. */ - custom_info_t *m_custom_info; + custom_edge_info *m_custom_info; private: DISABLE_COPY_AND_ASSIGN (exploded_edge); @@ -365,7 +359,7 @@ private: /* Extra data for an exploded_edge that represents dynamic call info ( calls that doesn't have an underlying superedge representing the call ). */ -class dynamic_call_info_t : public exploded_edge::custom_info_t +class dynamic_call_info_t : public custom_edge_info { public: dynamic_call_info_t (const gcall *dynamic_call, @@ -374,7 +368,7 @@ public: m_is_returning_call (is_returning_call) {} - void print (pretty_printer *pp) FINAL OVERRIDE + void print (pretty_printer *pp) const FINAL OVERRIDE { if (m_is_returning_call) pp_string (pp, "dynamic_return"); @@ -382,11 +376,12 @@ public: pp_string (pp, "dynamic_call"); } - void update_model (region_model *model, - const exploded_edge &eedge) FINAL OVERRIDE; + bool update_model (region_model *model, + const exploded_edge *eedge, + region_model_context *ctxt) const FINAL OVERRIDE; void add_events_to_path (checker_path *emission_path, - const exploded_edge &eedge) FINAL OVERRIDE; + const exploded_edge &eedge) const FINAL OVERRIDE; private: const gcall *m_dynamic_call; const bool m_is_returning_call; @@ -396,7 +391,7 @@ private: /* Extra data for an exploded_edge that represents a rewind from a longjmp to a setjmp (or from a siglongjmp to a sigsetjmp). */ -class rewind_info_t : public exploded_edge::custom_info_t +class rewind_info_t : public custom_edge_info { public: rewind_info_t (const setjmp_record &setjmp_record, @@ -405,16 +400,17 @@ public: m_longjmp_call (longjmp_call) {} - void print (pretty_printer *pp) FINAL OVERRIDE + void print (pretty_printer *pp) const FINAL OVERRIDE { pp_string (pp, "rewind"); } - void update_model (region_model *model, - const exploded_edge &eedge) FINAL OVERRIDE; + bool update_model (region_model *model, + const exploded_edge *eedge, + region_model_context *ctxt) const FINAL OVERRIDE; void add_events_to_path (checker_path *emission_path, - const exploded_edge &eedge) FINAL OVERRIDE; + const exploded_edge &eedge) const FINAL OVERRIDE; const program_point &get_setjmp_point () const { @@ -829,7 +825,7 @@ public: exploded_node *enode_for_diag); exploded_edge *add_edge (exploded_node *src, exploded_node *dest, const superedge *sedge, - exploded_edge::custom_info_t *custom = NULL); + custom_edge_info *custom = NULL); per_program_point_data * get_or_create_per_program_point_data (const program_point &); diff --git a/gcc/analyzer/program-state.cc b/gcc/analyzer/program-state.cc index ea53c61f497..c1ff0d88bb8 100644 --- a/gcc/analyzer/program-state.cc +++ b/gcc/analyzer/program-state.cc @@ -1013,7 +1013,7 @@ program_state::on_edge (exploded_graph &eg, impl_region_model_context ctxt (eg, enode, &enode->get_state (), this, - uncertainty, + uncertainty, NULL, last_stmt); if (!m_region_model->maybe_update_for_edge (*succ, last_stmt, @@ -1052,6 +1052,7 @@ program_state::push_call (exploded_graph &eg, &enode->get_state (), this, uncertainty, + NULL, last_stmt); m_region_model->update_for_gcall (call_stmt, &ctxt); } @@ -1074,6 +1075,7 @@ program_state::returning_call (exploded_graph &eg, &enode->get_state (), this, uncertainty, + NULL, last_stmt); m_region_model->update_for_return_gcall (call_stmt, &ctxt); } @@ -1152,7 +1154,7 @@ program_state::prune_for_point (exploded_graph &eg, impl_region_model_context ctxt (eg, enode_for_diag, this, &new_state, - uncertainty, + uncertainty, NULL, point.get_stmt ()); detect_leaks (*this, new_state, NULL, eg.get_ext_state (), &ctxt); } diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc index e5a6cb2e154..875719f9989 100644 --- a/gcc/analyzer/region-model-impl-calls.cc +++ b/gcc/analyzer/region-model-impl-calls.cc @@ -56,6 +56,7 @@ 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 "analyzer/call-info.h" #include "gimple-pretty-print.h" #if ENABLE_ANALYZER @@ -158,6 +159,15 @@ call_details::get_arg_string_literal (unsigned idx) const return NULL; } +/* Attempt to get the fndecl used at this call, if known, or NULL_TREE + otherwise. */ + +tree +call_details::get_fndecl_for_call () const +{ + return m_model->get_fndecl_for_call (m_call, m_ctxt); +} + /* Dump a multiline representation of this call to PP. */ void @@ -486,15 +496,169 @@ region_model::impl_call_operator_delete (const call_details &cd) } } -/* Handle the on_call_pre part of "realloc". */ +/* Handle the on_call_post part of "realloc": + + void *realloc(void *ptr, size_t size); + + realloc(3) is awkward, since it has various different outcomes + that are best modelled as separate exploded nodes/edges. + + We first check for sm-state, in + malloc_state_machine::on_realloc_call, so that we + can complain about issues such as realloc of a non-heap + pointer, and terminate the path for such cases (and issue + the complaints at the call's exploded node). + + Assuming that these checks pass, we split the path here into + three special cases (and terminate the "standard" path): + (A) failure, returning NULL + (B) success, growing the buffer in-place without moving it + (C) success, allocating a new buffer, copying the content + of the old buffer to it, and freeing the old buffer. + + Each of these has a custom_edge_info subclass, which updates + the region_model and sm-state of the destination state. */ void -region_model::impl_call_realloc (const call_details &) +region_model::impl_call_realloc (const call_details &cd) { - /* Currently we don't support bifurcating state, so there's no good - way to implement realloc(3). - For now, malloc_state_machine::on_realloc_call has a minimal - implementation to suppress false positives. */ + /* Three custom subclasses of custom_edge_info, for handling the various + outcomes of "realloc". */ + + /* Concrete custom_edge_info: a realloc call that fails, returning NULL. */ + class failure : public failed_call_info + { + public: + failure (const call_details &cd) + : failed_call_info (cd) + { + } + + bool update_model (region_model *model, + const exploded_edge *, + region_model_context *ctxt) const FINAL OVERRIDE + { + /* Return NULL; everything else is unchanged. */ + const call_details cd (get_call_details (model, ctxt)); + const svalue *zero + = model->m_mgr->get_or_create_int_cst (cd.get_lhs_type (), 0); + model->set_value (cd.get_lhs_region (), + zero, + cd.get_ctxt ()); + return true; + } + }; + + /* Concrete custom_edge_info: a realloc call that succeeds, growing + the existing buffer without moving it. */ + class success_no_move : public call_info + { + public: + success_no_move (const call_details &cd) + : call_info (cd) + { + } + + label_text get_desc (bool can_colorize) const FINAL OVERRIDE + { + return make_label_text (can_colorize, + "when %qE succeeds, without moving buffer", + get_fndecl ()); + } + + bool update_model (region_model *model, + const exploded_edge *, + region_model_context *ctxt) const FINAL OVERRIDE + { + /* Update size of buffer and return the ptr unchanged. */ + const call_details cd (get_call_details (model, ctxt)); + const svalue *ptr_sval = cd.get_arg_svalue (0); + const svalue *size_sval = cd.get_arg_svalue (1); + if (const region *buffer_reg = ptr_sval->maybe_get_region ()) + model->set_dynamic_extents (buffer_reg, size_sval); + model->set_value (cd.get_lhs_region (), ptr_sval, cd.get_ctxt ()); + const svalue *zero + = model->m_mgr->get_or_create_int_cst (cd.get_lhs_type (), 0); + return model->add_constraint (ptr_sval, NE_EXPR, zero, cd.get_ctxt ()); + } + }; + + /* Concrete custom_edge_info: a realloc call that succeeds, freeing + the existing buffer and moving the content to a freshly allocated + buffer. */ + class success_with_move : public call_info + { + public: + success_with_move (const call_details &cd) + : call_info (cd) + { + } + + label_text get_desc (bool can_colorize) const FINAL OVERRIDE + { + return make_label_text (can_colorize, + "when %qE succeeds, moving buffer", + get_fndecl ()); + } + bool update_model (region_model *model, + const exploded_edge *, + region_model_context *ctxt) const FINAL OVERRIDE + { + const call_details cd (get_call_details (model, ctxt)); + const svalue *old_ptr_sval = cd.get_arg_svalue (0); + const svalue *new_size_sval = cd.get_arg_svalue (1); + + /* Create the new region. */ + const region *new_reg + = model->create_region_for_heap_alloc (new_size_sval); + const svalue *new_ptr_sval + = model->m_mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg); + if (cd.get_lhs_type ()) + cd.maybe_set_lhs (new_ptr_sval); + + if (const region *freed_reg = old_ptr_sval->maybe_get_region ()) + { + /* Copy the data. */ + const svalue *old_size_sval = model->get_dynamic_extents (freed_reg); + if (old_size_sval) + { + const region *sized_old_reg + = model->m_mgr->get_sized_region (freed_reg, NULL, + old_size_sval); + const svalue *buffer_content_sval + = model->get_store_value (sized_old_reg, cd.get_ctxt ()); + model->set_value (new_reg, buffer_content_sval, cd.get_ctxt ()); + } + + /* Free the old region, so that pointers to the old buffer become + invalid. */ + + /* If the ptr points to an underlying heap region, delete it, + poisoning pointers. */ + model->unbind_region_and_descendents (freed_reg, POISON_KIND_FREED); + model->m_dynamic_extents.remove (freed_reg); + } + + /* Update the sm-state: mark the old_ptr_sval as "freed", + and the new_ptr_sval as "nonnull". */ + model->on_realloc_with_move (cd, old_ptr_sval, new_ptr_sval); + + const svalue *zero + = model->m_mgr->get_or_create_int_cst (cd.get_lhs_type (), 0); + return model->add_constraint (new_ptr_sval, NE_EXPR, zero, + cd.get_ctxt ()); + } + }; + + /* Body of region_model::impl_call_realloc. */ + + if (cd.get_ctxt ()) + { + cd.get_ctxt ()->bifurcate (new failure (cd)); + cd.get_ctxt ()->bifurcate (new success_no_move (cd)); + cd.get_ctxt ()->bifurcate (new success_with_move (cd)); + cd.get_ctxt ()->terminate_path (); + } } /* Handle the on_call_pre part of "strcpy" and "__builtin_strcpy_chk". */ diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 787f2ed33c0..3bfc4ba53ee 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -1132,7 +1132,6 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt, return false; break; case BUILT_IN_REALLOC: - impl_call_realloc (cd); return false; case BUILT_IN_STRCPY: case BUILT_IN_STRCPY_CHK: @@ -1276,9 +1275,9 @@ region_model::on_call_post (const gcall *call, { if (tree callee_fndecl = get_fndecl_for_call (call, ctxt)) { + call_details cd (call, this, ctxt); if (is_named_call_p (callee_fndecl, "free", call, 1)) { - call_details cd (call, this, ctxt); impl_call_free (cd); return; } @@ -1286,7 +1285,6 @@ region_model::on_call_post (const gcall *call, || is_named_call_p (callee_fndecl, "operator delete", call, 2) || is_named_call_p (callee_fndecl, "operator delete []", call, 1)) { - call_details cd (call, this, ctxt); impl_call_operator_delete (cd); return; } @@ -1294,10 +1292,19 @@ region_model::on_call_post (const gcall *call, __attribute__((malloc(FOO)))? */ if (lookup_attribute ("*dealloc", DECL_ATTRIBUTES (callee_fndecl))) { - call_details cd (call, this, ctxt); impl_deallocation_call (cd); return; } + if (fndecl_built_in_p (callee_fndecl, BUILT_IN_NORMAL) + && gimple_builtin_call_types_compatible_p (call, callee_fndecl)) + switch (DECL_UNCHECKED_FUNCTION_CODE (callee_fndecl)) + { + default: + break; + case BUILT_IN_REALLOC: + impl_call_realloc (cd); + return; + } } if (unknown_side_effects) @@ -3765,6 +3772,19 @@ region_model::unset_dynamic_extents (const region *reg) m_dynamic_extents.remove (reg); } +/* class noop_region_model_context : public region_model_context. */ + +void +noop_region_model_context::bifurcate (custom_edge_info *info) +{ + delete info; +} + +void +noop_region_model_context::terminate_path () +{ +} + /* struct model_merger. */ /* Dump a multiline representation of this merger to PP. */ diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index f2c82b0dd80..5fabf7881e2 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -487,11 +487,15 @@ public: unsigned num_args () const; + const gcall *get_call_stmt () const { return m_call; } + tree get_arg_tree (unsigned idx) const; tree get_arg_type (unsigned idx) const; const svalue *get_arg_svalue (unsigned idx) const; const char *get_arg_string_literal (unsigned idx) const; + tree get_fndecl_for_call () const; + void dump_to_pp (pretty_printer *pp, bool simple) const; void dump (bool simple) const; @@ -732,6 +736,11 @@ class region_model const svalue *get_capacity (const region *reg) const; + /* Implemented in sm-malloc.cc */ + void on_realloc_with_move (const call_details &cd, + const svalue *old_ptr_sval, + const svalue *new_ptr_sval); + private: const region *get_lvalue_1 (path_var pv, region_model_context *ctxt) const; const svalue *get_rvalue_1 (path_var pv, region_model_context *ctxt) const; @@ -867,6 +876,21 @@ class region_model_context /* Hook for clients to purge state involving SVAL. */ virtual void purge_state_involving (const svalue *sval) = 0; + + /* Hook for clients to split state with a non-standard path. + Take ownership of INFO. */ + virtual void bifurcate (custom_edge_info *info) = 0; + + /* Hook for clients to terminate the standard path. */ + virtual void terminate_path () = 0; + + virtual const extrinsic_state *get_ext_state () const = 0; + + /* Hook for clients to access the "malloc" state machine in + any underlying program_state. */ + virtual bool get_malloc_map (sm_state_map **out_smap, + const state_machine **out_sm, + unsigned *out_sm_idx) = 0; }; /* A "do nothing" subclass of region_model_context. */ @@ -899,6 +923,18 @@ public: uncertainty_t *get_uncertainty () OVERRIDE { return NULL; } void purge_state_involving (const svalue *sval ATTRIBUTE_UNUSED) OVERRIDE {} + + void bifurcate (custom_edge_info *info) OVERRIDE; + void terminate_path () OVERRIDE; + + const extrinsic_state *get_ext_state () const OVERRIDE { return NULL; } + + bool get_malloc_map (sm_state_map **, + const state_machine **, + unsigned *) OVERRIDE + { + return false; + } }; /* A subclass of region_model_context for determining if operations fail diff --git a/gcc/analyzer/sm-malloc.cc b/gcc/analyzer/sm-malloc.cc index 74c6fee2638..bf5e3c365b4 100644 --- a/gcc/analyzer/sm-malloc.cc +++ b/gcc/analyzer/sm-malloc.cc @@ -45,6 +45,7 @@ along with GCC; see the file COPYING3. If not see #include "stringpool.h" #include "attribs.h" #include "analyzer/function-set.h" +#include "analyzer/program-state.h" #if ENABLE_ANALYZER @@ -387,6 +388,12 @@ public: static bool unaffected_by_call_p (tree fndecl); + void on_realloc_with_move (region_model *model, + sm_state_map *smap, + const svalue *old_ptr_sval, + const svalue *new_ptr_sval, + const extrinsic_state &ext_state) const; + standard_deallocator_set m_free; standard_deallocator_set m_scalar_delete; standard_deallocator_set m_vector_delete; @@ -1836,54 +1843,65 @@ malloc_state_machine::on_deallocator_call (sm_context *sm_ctxt, } } -/* Implementation of realloc(3): - - void *realloc(void *ptr, size_t size); - - realloc(3) is awkward. +/* Handle a call to "realloc". + Check for free of non-heap or mismatching allocators, + transitioning to the "stop" state for such cases. - We currently don't have a way to express multiple possible outcomes - from a function call, "bifurcating" the state such as: - - success: non-NULL is returned - - failure: NULL is returned, existing buffer is not freed. - or even an N-way state split e.g.: - - buffer grew successfully in-place - - buffer was successfully moved to a larger allocation - - buffer was successfully contracted - - realloc failed, returning NULL, without freeing existing buffer. - (PR analyzer/99260 tracks this) - - Given that we can currently only express one outcome, eliminate - false positives by dropping state from the buffer. */ + Otherwise, region_model::impl_call_realloc will later + get called (which will handle other sm-state transitions + when the state is bifurcated). */ void malloc_state_machine::on_realloc_call (sm_context *sm_ctxt, - const supernode *node ATTRIBUTE_UNUSED, + const supernode *node, const gcall *call) const { - tree ptr = gimple_call_arg (call, 0); + const unsigned argno = 0; + const deallocator *d = &m_realloc; + + tree arg = gimple_call_arg (call, argno); - state_t state = sm_ctxt->get_state (call, ptr); + state_t state = sm_ctxt->get_state (call, arg); - /* Detect mismatches. */ if (unchecked_p (state) || nonnull_p (state)) { const allocation_state *astate = as_a_allocation_state (state); gcc_assert (astate->m_deallocators); - if (astate->m_deallocators != &m_free) + if (!astate->m_deallocators->contains_p (&m_free.m_deallocator)) { /* Wrong allocator. */ - tree diag_ptr = sm_ctxt->get_diagnostic_tree (ptr); + tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); pending_diagnostic *pd - = new mismatching_deallocation (*this, diag_ptr, + = new mismatching_deallocation (*this, diag_arg, astate->m_deallocators, - &m_realloc); - sm_ctxt->warn (node, call, ptr, pd); + d); + sm_ctxt->warn (node, call, arg, pd); + sm_ctxt->set_next_state (call, arg, m_stop); + if (path_context *path_ctxt = sm_ctxt->get_path_context ()) + path_ctxt->terminate_path (); } } - - /* Transition ptr to "stop" state. */ - sm_ctxt->set_next_state (call, ptr, m_stop); + else if (state == m_free.m_deallocator.m_freed) + { + /* freed -> stop, with warning. */ + tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); + sm_ctxt->warn (node, call, arg, + new double_free (*this, diag_arg, "free")); + sm_ctxt->set_next_state (call, arg, m_stop); + if (path_context *path_ctxt = sm_ctxt->get_path_context ()) + path_ctxt->terminate_path (); + } + else if (state == m_non_heap) + { + /* non-heap -> stop, with warning. */ + tree diag_arg = sm_ctxt->get_diagnostic_tree (arg); + sm_ctxt->warn (node, call, arg, + new free_of_non_heap (*this, diag_arg, + d->m_name)); + sm_ctxt->set_next_state (call, arg, m_stop); + if (path_context *path_ctxt = sm_ctxt->get_path_context ()) + path_ctxt->terminate_path (); + } } /* Implementation of state_machine::on_phi vfunc for malloc_state_machine. */ @@ -2015,6 +2033,30 @@ malloc_state_machine::on_zero_assignment (sm_context *sm_ctxt, sm_ctxt->set_next_state (stmt, lhs, m_null); } +/* Special-case hook for handling realloc, for the "success with move to + a new buffer" case, marking OLD_PTR_SVAL as freed and NEW_PTR_SVAL as + non-null. + + This is similar to on_deallocator_call and on_allocator_call, + but the checks happen in on_realloc_call, and by splitting the states. */ + +void +malloc_state_machine:: +on_realloc_with_move (region_model *model, + sm_state_map *smap, + const svalue *old_ptr_sval, + const svalue *new_ptr_sval, + const extrinsic_state &ext_state) const +{ + smap->set_state (model, old_ptr_sval, + m_free.m_deallocator.m_freed, + NULL, ext_state); + + smap->set_state (model, new_ptr_sval, + m_free.m_nonnull, + NULL, ext_state); +} + } // anonymous namespace /* Internal interface to this file. */ @@ -2025,6 +2067,40 @@ make_malloc_state_machine (logger *logger) return new malloc_state_machine (logger); } +/* Specialcase hook for handling realloc, for use by + region_model::impl_call_realloc::success_with_move::update_model. */ + +void +region_model::on_realloc_with_move (const call_details &cd, + const svalue *old_ptr_sval, + const svalue *new_ptr_sval) +{ + region_model_context *ctxt = cd.get_ctxt (); + if (!ctxt) + return; + const extrinsic_state *ext_state = ctxt->get_ext_state (); + if (!ext_state) + return; + + sm_state_map *smap; + const state_machine *sm; + unsigned sm_idx; + if (!ctxt->get_malloc_map (&smap, &sm, &sm_idx)) + return; + + gcc_assert (smap); + gcc_assert (sm); + + const malloc_state_machine &malloc_sm + = (const malloc_state_machine &)*sm; + + malloc_sm.on_realloc_with_move (this, + smap, + old_ptr_sval, + new_ptr_sval, + *ext_state); +} + } // namespace ana #endif /* #if ENABLE_ANALYZER */ diff --git a/gcc/analyzer/sm-signal.cc b/gcc/analyzer/sm-signal.cc index fcbf322f502..e8cbe2d3f82 100644 --- a/gcc/analyzer/sm-signal.cc +++ b/gcc/analyzer/sm-signal.cc @@ -206,10 +206,10 @@ update_model_for_signal_handler (region_model *model, /* Custom exploded_edge info: entry into a signal-handler. */ -class signal_delivery_edge_info_t : public exploded_edge::custom_info_t +class signal_delivery_edge_info_t : public custom_edge_info { public: - void print (pretty_printer *pp) FINAL OVERRIDE + void print (pretty_printer *pp) const FINAL OVERRIDE { pp_string (pp, "signal delivered"); } @@ -220,15 +220,18 @@ public: return custom_obj; } - void update_model (region_model *model, - const exploded_edge &eedge) FINAL OVERRIDE + bool update_model (region_model *model, + const exploded_edge *eedge, + region_model_context *) const FINAL OVERRIDE { - update_model_for_signal_handler (model, eedge.m_dest->get_function ()); + gcc_assert (eedge); + update_model_for_signal_handler (model, eedge->m_dest->get_function ()); + return true; } void add_events_to_path (checker_path *emission_path, const exploded_edge &eedge ATTRIBUTE_UNUSED) - FINAL OVERRIDE + const FINAL OVERRIDE { emission_path->add_event (new precanned_custom_event diff --git a/gcc/analyzer/sm.h b/gcc/analyzer/sm.h index 6bb036e343b..02faffbff99 100644 --- a/gcc/analyzer/sm.h +++ b/gcc/analyzer/sm.h @@ -257,6 +257,11 @@ public: Otherwise return NULL_TREE. */ virtual tree is_zero_assignment (const gimple *stmt) = 0; + virtual path_context *get_path_context () const + { + return NULL; + } + protected: sm_context (int sm_idx, const state_machine &sm) : m_sm_idx (sm_idx), m_sm (sm) {} diff --git a/gcc/analyzer/svalue.cc b/gcc/analyzer/svalue.cc index 691316179f0..5f2fe4c6449 100644 --- a/gcc/analyzer/svalue.cc +++ b/gcc/analyzer/svalue.cc @@ -105,7 +105,8 @@ svalue::to_json () const tree svalue::maybe_get_constant () const { - if (const constant_svalue *cst_sval = dyn_cast_constant_svalue ()) + const svalue *sval = unwrap_any_unmergeable (); + if (const constant_svalue *cst_sval = sval->dyn_cast_constant_svalue ()) return cst_sval->get_constant (); else return NULL_TREE; diff --git a/gcc/testsuite/gcc.dg/analyzer/capacity-2.c b/gcc/testsuite/gcc.dg/analyzer/capacity-2.c index 9f92bcfc0a4..2db1b3fa200 100644 --- a/gcc/testsuite/gcc.dg/analyzer/capacity-2.c +++ b/gcc/testsuite/gcc.dg/analyzer/capacity-2.c @@ -8,7 +8,8 @@ void * test_realloc_1 (void *p, size_t new_sz) { void *q = realloc (p, new_sz); - __analyzer_dump_capacity (q); /* { dg-warning "capacity: 'UNKNOWN\\(sizetype\\)'" } */ + __analyzer_dump_capacity (q); /* { dg-warning "capacity: 'UNKNOWN\\(sizetype\\)'" "failure" } */ + /* { dg-warning "capacity: 'INIT_VAL\\(new_sz\[^\n\r\]*\\)'" "success" { target *-*-* } .-1 } */ return q; } @@ -18,8 +19,9 @@ test_realloc_2 (size_t sz_a, size_t sz_b) void *p = malloc (sz_a); __analyzer_dump_capacity (p); /* { dg-warning "capacity: 'INIT_VAL\\(sz_a_\[^\n\r\]*\\)'" } */ void *q = realloc (p, sz_b); - __analyzer_dump_capacity (q); /* { dg-warning "capacity: 'UNKNOWN\\(sizetype\\)'" } */ - return p; + __analyzer_dump_capacity (q); /* { dg-warning "capacity: 'UNKNOWN\\(sizetype\\)'" "failure" } */ + /* { dg-warning "capacity: 'INIT_VAL\\(sz_b\[^\n\r\]*\\)'" "success" { target *-*-* } .-1 } */ + return q; /* { dg-warning "leak of 'p'" } */ } void * diff --git a/gcc/testsuite/gcc.dg/analyzer/pr99193-1.c b/gcc/testsuite/gcc.dg/analyzer/pr99193-1.c index c6179e98948..459357cf138 100644 --- a/gcc/testsuite/gcc.dg/analyzer/pr99193-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/pr99193-1.c @@ -1,3 +1,5 @@ +/* { dg-additional-options "-Wno-analyzer-too-complex" } */ + /* Verify absence of false positive from -Wanalyzer-mismatching-deallocation on realloc(3). Based on https://github.com/libguestfs/libguestfs/blob/f19fd566f6387ce7e4d82409528c9dde374d25e0/daemon/command.c#L115 diff --git a/gcc/testsuite/gcc.dg/analyzer/pr99193-3.c b/gcc/testsuite/gcc.dg/analyzer/pr99193-3.c index 3e7ffd65212..d64b0458e9e 100644 --- a/gcc/testsuite/gcc.dg/analyzer/pr99193-3.c +++ b/gcc/testsuite/gcc.dg/analyzer/pr99193-3.c @@ -1,3 +1,5 @@ +/* { dg-additional-options "-Wno-analyzer-too-complex" } */ + /* Verify absence of false positive from -Wanalyzer-mismatching-deallocation on realloc(3). Based on https://github.com/libguestfs/libguestfs/blob/f19fd566f6387ce7e4d82409528c9dde374d25e0/daemon/debug.c#L115 diff --git a/gcc/testsuite/gcc.dg/analyzer/realloc-1.c b/gcc/testsuite/gcc.dg/analyzer/realloc-1.c index a6c6bfc3b22..606a19abe77 100644 --- a/gcc/testsuite/gcc.dg/analyzer/realloc-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/realloc-1.c @@ -1,3 +1,5 @@ +/* { dg-additional-options "-Wno-free-nonheap-object" } */ + typedef __SIZE_TYPE__ size_t; #define NULL ((void *)0) @@ -20,11 +22,10 @@ void *test_1 (void *ptr) void *test_2 (void *ptr) { - void *p = malloc (1024); - p = realloc (p, 4096); - /* TODO: should warn about the leak when the above call fails (PR analyzer/99260). */ + void *p = malloc (1024); /* { dg-message "allocated here" } */ + p = realloc (p, 4096); /* { dg-message "when 'realloc' fails" } */ free (p); -} +} /* { dg-warning "leak of 'p'" } */ // ideally this would be on the realloc stmt void *test_3 (void *ptr) { @@ -44,8 +45,8 @@ void *test_4 (void) int *test_5 (int *p) { *p = 42; - int *q = realloc (p, sizeof(int) * 4); - *q = 43; /* { dg-warning "possibly-NULL 'q'" "PR analyzer/99260" { xfail *-*-* } } */ + int *q = realloc (p, sizeof(int) * 4); /* { dg-message "when 'realloc' fails" } */ + *q = 43; /* { dg-warning "dereference of NULL 'q'" } */ return q; } @@ -53,3 +54,37 @@ void test_6 (size_t sz) { void *p = realloc (NULL, sz); } /* { dg-warning "leak of 'p'" } */ + +/* The analyzer should complain about realloc of non-heap. */ + +void *test_7 (size_t sz) +{ + char buf[100]; + void *p = realloc (&buf, sz); /* { dg-warning "'realloc' of '&buf' which points to memory not on the heap" } */ + return p; +} + +/* Mismatched allocator. */ + +struct foo +{ + int m_int; +}; + +extern void foo_release (struct foo *); +extern struct foo *foo_acquire (void) + __attribute__ ((malloc (foo_release))); + +void test_8 (void) +{ + struct foo *p = foo_acquire (); + void *q = realloc (p, 1024); /* { dg-warning "'p' should have been deallocated with 'foo_release' but was deallocated with 'realloc'" } */ +} + +/* We should complain about realloc on a freed pointer. */ + +void test_9 (void *p) +{ + free (p); + void *q = realloc (p, 1024); /* { dg-warning "double-'free' of 'p'" } */ +} diff --git a/gcc/testsuite/gcc.dg/analyzer/realloc-2.c b/gcc/testsuite/gcc.dg/analyzer/realloc-2.c new file mode 100644 index 00000000000..a39775354a3 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/realloc-2.c @@ -0,0 +1,80 @@ +#include "analyzer-decls.h" + +typedef __SIZE_TYPE__ size_t; + +#define NULL ((void *)0) + +extern void *malloc (size_t __size) + __attribute__ ((__nothrow__ , __leaf__)) + __attribute__ ((__malloc__)) + __attribute__ ((__alloc_size__ (1))); +extern void *realloc (void *__ptr, size_t __size) + __attribute__ ((__nothrow__ , __leaf__)) + __attribute__ ((__warn_unused_result__)) + __attribute__ ((__alloc_size__ (2))); +extern void free (void *__ptr) + __attribute__ ((__nothrow__ , __leaf__)); + +char *test_8 (size_t sz) +{ + char *p, *q; + + p = malloc (3); + if (!p) + return NULL; + + __analyzer_dump_capacity (p); /* { dg-warning "capacity: '\\(size_t\\)3'" } */ + + p[0] = 'a'; + p[1] = 'b'; + p[2] = 'c'; + + __analyzer_eval (p[0] == 'a'); /* { dg-warning "TRUE" } */ + __analyzer_eval (p[1] == 'b'); /* { dg-warning "TRUE" } */ + __analyzer_eval (p[2] == 'c'); /* { dg-warning "TRUE" } */ + + q = realloc (p, 6); + + /* We should have 3 nodes, corresponding to "failure", + "success without moving", and "success with moving". */ + __analyzer_dump_exploded_nodes (0); /* { dg-warning "3 processed enodes" } */ + + if (q) + { + __analyzer_dump_capacity (q); /* { dg-warning "capacity: '\\(size_t\\)6'" } */ + q[3] = 'd'; + q[4] = 'e'; + q[5] = 'f'; + if (q == p) + { + /* "realloc" success, growing the buffer in-place. */ + __analyzer_eval (p[0] == 'a'); /* { dg-warning "TRUE" } */ + __analyzer_eval (p[1] == 'b'); /* { dg-warning "TRUE" } */ + __analyzer_eval (p[2] == 'c'); /* { dg-warning "TRUE" } */ + // TODO + } + else + { + /* "realloc" success, moving the buffer (and thus freeing "p"). */ + __analyzer_eval (q[0] == 'a'); /* { dg-warning "TRUE" } */ + __analyzer_eval (q[1] == 'b'); /* { dg-warning "TRUE" } */ + __analyzer_eval (q[2] == 'c'); /* { dg-warning "TRUE" } */ + __analyzer_eval (p[0] == 'a'); /* { dg-warning "UNKNOWN" "unknown" } */ + /* { dg-warning "use after 'free' of 'p'" "use after free" { target *-*-* } .-1 } */ + } + __analyzer_eval (q[3] == 'd'); /* { dg-warning "TRUE" } */ + __analyzer_eval (q[4] == 'e'); /* { dg-warning "TRUE" } */ + __analyzer_eval (q[5] == 'f'); /* { dg-warning "TRUE" } */ + } + else + { + /* "realloc" failure. p should be unchanged. */ + __analyzer_dump_capacity (p); /* { dg-warning "capacity: '\\(size_t\\)3'" } */ + __analyzer_eval (p[0] == 'a'); /* { dg-warning "TRUE" } */ + __analyzer_eval (p[1] == 'b'); /* { dg-warning "TRUE" } */ + __analyzer_eval (p[2] == 'c'); /* { dg-warning "TRUE" } */ + return p; + } + + return q; +} -- 2.26.3