Mercurial > hg > CbC > CbC_gcc
view gcc/analyzer/diagnostic-manager.cc @ 158:494b0b89df80 default tip
...
author | Shinji KONO <kono@ie.u-ryukyu.ac.jp> |
---|---|
date | Mon, 25 May 2020 18:13:55 +0900 |
parents | 1830386684a0 |
children |
line wrap: on
line source
/* Classes for saving, deduplicating, and emitting analyzer diagnostics. Copyright (C) 2019-2020 Free Software Foundation, Inc. Contributed by David Malcolm <dmalcolm@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 "pretty-print.h" #include "gcc-rich-location.h" #include "gimple-pretty-print.h" #include "function.h" #include "diagnostic-core.h" #include "diagnostic-event-id.h" #include "diagnostic-path.h" #include "alloc-pool.h" #include "fibonacci_heap.h" #include "shortest-paths.h" #include "sbitmap.h" #include "tristate.h" #include "selftest.h" #include "ordered-hash-map.h" #include "analyzer/analyzer.h" #include "analyzer/analyzer-logging.h" #include "analyzer/sm.h" #include "analyzer/pending-diagnostic.h" #include "analyzer/diagnostic-manager.h" #include "analyzer/region-model.h" #include "analyzer/constraint-manager.h" #include "cfg.h" #include "basic-block.h" #include "gimple.h" #include "gimple-iterator.h" #include "cgraph.h" #include "digraph.h" #include "analyzer/supergraph.h" #include "analyzer/call-string.h" #include "analyzer/program-point.h" #include "analyzer/program-state.h" #include "analyzer/exploded-graph.h" #include "analyzer/checker-path.h" #if ENABLE_ANALYZER namespace ana { /* class saved_diagnostic. */ /* saved_diagnostic's ctor. Take ownership of D and STMT_FINDER. */ saved_diagnostic::saved_diagnostic (const state_machine *sm, const exploded_node *enode, const supernode *snode, const gimple *stmt, stmt_finder *stmt_finder, tree var, state_machine::state_t state, pending_diagnostic *d) : m_sm (sm), m_enode (enode), m_snode (snode), m_stmt (stmt), /* stmt_finder could be on-stack; we want our own copy that can outlive that. */ m_stmt_finder (stmt_finder ? stmt_finder->clone () : NULL), m_var (var), m_state (state), m_d (d), m_trailing_eedge (NULL) { gcc_assert (m_stmt || m_stmt_finder); /* We must have an enode in order to be able to look for paths through the exploded_graph to this diagnostic. */ gcc_assert (m_enode); } /* saved_diagnostic's dtor. */ saved_diagnostic::~saved_diagnostic () { delete m_stmt_finder; delete m_d; } bool saved_diagnostic::operator== (const saved_diagnostic &other) const { return (m_sm == other.m_sm /* We don't compare m_enode. */ && m_snode == other.m_snode && m_stmt == other.m_stmt /* We don't compare m_stmt_finder. */ && pending_diagnostic::same_tree_p (m_var, other.m_var) && m_state == other.m_state && m_d->equal_p (*other.m_d) && m_trailing_eedge == other.m_trailing_eedge); } /* class diagnostic_manager. */ /* diagnostic_manager's ctor. */ diagnostic_manager::diagnostic_manager (logger *logger, int verbosity) : log_user (logger), m_verbosity (verbosity) { } /* Queue pending_diagnostic D at ENODE for later emission. */ void diagnostic_manager::add_diagnostic (const state_machine *sm, const exploded_node *enode, const supernode *snode, const gimple *stmt, stmt_finder *finder, tree var, state_machine::state_t state, pending_diagnostic *d) { LOG_FUNC (get_logger ()); /* We must have an enode in order to be able to look for paths through the exploded_graph to the diagnostic. */ gcc_assert (enode); saved_diagnostic *sd = new saved_diagnostic (sm, enode, snode, stmt, finder, var, state, d); m_saved_diagnostics.safe_push (sd); if (get_logger ()) log ("adding saved diagnostic %i at SN %i: %qs", m_saved_diagnostics.length () - 1, snode->m_index, d->get_kind ()); } /* Queue pending_diagnostic D at ENODE for later emission. */ void diagnostic_manager::add_diagnostic (const exploded_node *enode, const supernode *snode, const gimple *stmt, stmt_finder *finder, pending_diagnostic *d) { gcc_assert (enode); add_diagnostic (NULL, enode, snode, stmt, finder, NULL_TREE, 0, d); } /* A class for identifying sets of duplicated pending_diagnostic. We want to find the simplest dedupe_candidate amongst those that share a dedupe_key. */ class dedupe_key { public: dedupe_key (const saved_diagnostic &sd, const exploded_path &epath) : m_sd (sd), m_stmt (sd.m_stmt) { /* Support deferring the choice of stmt until after an emission path has been built, using an optional stmt_finder. */ if (m_stmt == NULL) { gcc_assert (sd.m_stmt_finder); m_stmt = sd.m_stmt_finder->find_stmt (epath); } gcc_assert (m_stmt); } hashval_t hash () const { inchash::hash hstate; hstate.add_ptr (m_stmt); // TODO: m_sd return hstate.end (); } bool operator== (const dedupe_key &other) const { return (m_sd == other.m_sd && m_stmt == other.m_stmt); } location_t get_location () const { return m_stmt->location; } /* A qsort comparator for use by dedupe_winners::emit_best to sort them into location_t order. */ static int comparator (const void *p1, const void *p2) { const dedupe_key *pk1 = *(const dedupe_key * const *)p1; const dedupe_key *pk2 = *(const dedupe_key * const *)p2; location_t loc1 = pk1->get_location (); location_t loc2 = pk2->get_location (); return linemap_compare_locations (line_table, loc2, loc1); } const saved_diagnostic &m_sd; const gimple *m_stmt; }; /* The value of a slot for a dedupe_key within dedupe_winners: the exploded_path for the best candidate for that key, and the number of duplicates seen so far. */ class dedupe_candidate { public: // has the exploded_path dedupe_candidate (const shortest_exploded_paths &sp, const saved_diagnostic &sd) : m_epath (sp.get_shortest_path (sd.m_enode)), m_num_dupes (0) { } unsigned length () const { return m_epath.length (); } const exploded_path &get_path () const { return m_epath; } void add_duplicate () { m_num_dupes++; } int get_num_dupes () const { return m_num_dupes; } private: exploded_path m_epath; public: int m_num_dupes; }; /* Traits for use by dedupe_winners. */ class dedupe_hash_map_traits { public: typedef const dedupe_key *key_type; typedef dedupe_candidate *value_type; typedef dedupe_candidate *compare_type; static inline hashval_t hash (const key_type &v) { return v->hash (); } static inline bool equal_keys (const key_type &k1, const key_type &k2) { return *k1 == *k2; } template <typename T> static inline void remove (T &) { // TODO } 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 = true; }; /* A class for deduplicating diagnostics and finding (and emitting) the best diagnostic within each partition. */ class dedupe_winners { public: ~dedupe_winners () { /* Delete all keys and candidates. */ for (map_t::iterator iter = m_map.begin (); iter != m_map.end (); ++iter) { delete (*iter).first; delete (*iter).second; } } /* Determine an exploded_path for SD using SP and, if it's feasible, determine if it's the best seen so far for its dedupe_key. Retain the winner for each dedupe_key, and discard the rest. */ void add (logger *logger, const shortest_exploded_paths &sp, const saved_diagnostic &sd) { /* Build a dedupe_candidate for SD. This uses SP to build an exploded_path. */ dedupe_candidate *dc = new dedupe_candidate (sp, sd); /* Verify that the epath is feasible. State-merging means that not every path in the epath corresponds to a feasible one w.r.t. states. Here we simply check each duplicate saved_diagnostic's shortest_path, and reject any that aren't feasible. This could introduce false negatives, as there could be longer feasible paths within the egraph. */ if (logger) logger->log ("considering %qs at SN: %i", sd.m_d->get_kind (), sd.m_snode->m_index); if (!dc->get_path ().feasible_p (logger)) { if (logger) logger->log ("rejecting %qs at SN: %i" " due to infeasible path", sd.m_d->get_kind (), sd.m_snode->m_index); delete dc; return; } else if (logger) logger->log ("accepting %qs at SN: %i with feasible path", sd.m_d->get_kind (), sd.m_snode->m_index); dedupe_key *key = new dedupe_key (sd, dc->get_path ()); if (dedupe_candidate **slot = m_map.get (key)) { if (logger) logger->log ("already have this dedupe_key"); (*slot)->add_duplicate (); if (dc->length () < (*slot)->length ()) { /* We've got a shorter path for the key; replace the current candidate. */ if (logger) logger->log ("length %i is better than existing length %i;" " taking over this dedupe_key", dc->length (), (*slot)->length ()); dc->m_num_dupes = (*slot)->get_num_dupes (); delete *slot; *slot = dc; } else /* We haven't beaten the current best candidate; drop the new candidate. */ { if (logger) logger->log ("length %i isn't better than existing length %i;" " dropping this candidate", dc->length (), (*slot)->length ()); delete dc; } delete key; } else { /* This is the first candidate for this key. */ m_map.put (key, dc); if (logger) logger->log ("first candidate for this dedupe_key"); } } /* Emit the simplest diagnostic within each set. */ void emit_best (diagnostic_manager *dm, const exploded_graph &eg) { LOG_SCOPE (dm->get_logger ()); /* Get keys into a vec for sorting. */ auto_vec<const dedupe_key *> keys (m_map.elements ()); for (map_t::iterator iter = m_map.begin (); iter != m_map.end (); ++iter) keys.quick_push ((*iter).first); dm->log ("# keys after de-duplication: %i", keys.length ()); /* Sort into a good emission order. */ keys.qsort (dedupe_key::comparator); /* Emit the best candidate for each key. */ int i; const dedupe_key *key; FOR_EACH_VEC_ELT (keys, i, key) { dedupe_candidate **slot = m_map.get (key); gcc_assert (*slot); const dedupe_candidate &dc = **slot; dm->emit_saved_diagnostic (eg, key->m_sd, dc.get_path (), key->m_stmt, dc.get_num_dupes ()); } } private: /* This maps from each dedupe_key to a current best dedupe_candidate. */ typedef hash_map<const dedupe_key *, dedupe_candidate *, dedupe_hash_map_traits> map_t; map_t m_map; }; /* Emit all saved diagnostics. */ void diagnostic_manager::emit_saved_diagnostics (const exploded_graph &eg) { LOG_SCOPE (get_logger ()); auto_timevar tv (TV_ANALYZER_DIAGNOSTICS); log ("# saved diagnostics: %i", m_saved_diagnostics.length ()); if (m_saved_diagnostics.length () == 0) return; /* Compute the shortest_paths once, sharing it between all diagnostics. */ shortest_exploded_paths sp (eg, eg.get_origin ()); /* Iterate through all saved diagnostics, adding them to a dedupe_winners instance. This partitions the saved diagnostics by dedupe_key, generating exploded_paths for them, and retaining the best one in each partition. */ dedupe_winners best_candidates; int i; saved_diagnostic *sd; FOR_EACH_VEC_ELT (m_saved_diagnostics, i, sd) best_candidates.add (get_logger (), sp, *sd); /* For each dedupe-key, call emit_saved_diagnostic on the "best" saved_diagnostic. */ best_candidates.emit_best (this, eg); } /* Given a saved_diagnostic SD at STMT with feasible path EPATH through EG, create an checker_path of suitable events and use it to call SD's underlying pending_diagnostic "emit" vfunc to emit a diagnostic. */ void diagnostic_manager::emit_saved_diagnostic (const exploded_graph &eg, const saved_diagnostic &sd, const exploded_path &epath, const gimple *stmt, int num_dupes) { LOG_SCOPE (get_logger ()); log ("sd: %qs at SN: %i", sd.m_d->get_kind (), sd.m_snode->m_index); log ("num dupes: %i", num_dupes); pretty_printer *pp = global_dc->printer->clone (); checker_path emission_path; /* Populate emission_path with a full description of EPATH. */ build_emission_path (eg, epath, &emission_path); /* Now prune it to just cover the most pertinent events. */ prune_path (&emission_path, sd.m_sm, sd.m_var, sd.m_state); /* Add a final event to the path, covering the diagnostic itself. We use the final enode from the epath, which might be different from the sd.m_enode, as the dedupe code doesn't care about enodes, just snodes. */ emission_path.add_final_event (sd.m_sm, epath.get_final_enode (), stmt, sd.m_var, sd.m_state); /* The "final" event might not be final; if the saved_diagnostic has a trailing eedge stashed, add any events for it. This is for use in handling longjmp, to show where a longjmp is rewinding to. */ if (sd.m_trailing_eedge) add_events_for_eedge (*sd.m_trailing_eedge, eg.get_ext_state (), &emission_path); emission_path.prepare_for_emission (sd.m_d); gcc_rich_location rich_loc (stmt->location); rich_loc.set_path (&emission_path); auto_diagnostic_group d; auto_cfun sentinel (sd.m_snode->m_fun); if (sd.m_d->emit (&rich_loc)) { if (num_dupes > 0) inform_n (stmt->location, num_dupes, "%i duplicate", "%i duplicates", num_dupes); } delete pp; } /* Given a state change to DST_REP, determine a tree that gives the origin of that state at STMT, using DST_STATE's region model, so that state changes based on assignments can be tracked back to their origins. For example, if we have (S1) _1 = malloc (64); (S2) EXPR = _1; then at stmt S2 we can get the origin of EXPR's state as being _1, and thus track the allocation back to S1. */ static tree get_any_origin (const gimple *stmt, tree dst_rep, const program_state &dst_state) { if (!stmt) return NULL_TREE; gcc_assert (dst_rep); if (const gassign *assign = dyn_cast <const gassign *> (stmt)) { tree lhs = gimple_assign_lhs (assign); /* Use region IDs to compare lhs with DST_REP. */ if (dst_state.m_region_model->get_lvalue (lhs, NULL) == dst_state.m_region_model->get_lvalue (dst_rep, NULL)) { tree rhs1 = gimple_assign_rhs1 (assign); enum tree_code op = gimple_assign_rhs_code (assign); switch (op) { default: //gcc_unreachable (); // TODO break; case COMPONENT_REF: case SSA_NAME: return rhs1; } } } return NULL_TREE; } /* Emit a "path" of events to EMISSION_PATH describing the exploded path EPATH within EG. */ void diagnostic_manager::build_emission_path (const exploded_graph &eg, const exploded_path &epath, checker_path *emission_path) const { LOG_SCOPE (get_logger ()); const extrinsic_state &ext_state = eg.get_ext_state (); for (unsigned i = 0; i < epath.m_edges.length (); i++) { const exploded_edge *eedge = epath.m_edges[i]; add_events_for_eedge (*eedge, ext_state, emission_path); } } /* Subclass of state_change_visitor that creates state_change_event instances. */ class state_change_event_creator : public state_change_visitor { public: state_change_event_creator (const exploded_edge &eedge, checker_path *emission_path) : m_eedge (eedge), m_emission_path (emission_path) {} bool on_global_state_change (const state_machine &sm, state_machine::state_t src_sm_val, state_machine::state_t dst_sm_val) FINAL OVERRIDE { const exploded_node *src_node = m_eedge.m_src; const program_point &src_point = src_node->get_point (); const int src_stack_depth = src_point.get_stack_depth (); const exploded_node *dst_node = m_eedge.m_dest; const gimple *stmt = src_point.get_stmt (); const supernode *supernode = src_point.get_supernode (); const program_state &dst_state = dst_node->get_state (); int stack_depth = src_stack_depth; m_emission_path->add_event (new state_change_event (supernode, stmt, stack_depth, sm, NULL_TREE, src_sm_val, dst_sm_val, NULL_TREE, dst_state)); return false; } bool on_state_change (const state_machine &sm, state_machine::state_t src_sm_val, state_machine::state_t dst_sm_val, tree dst_rep, svalue_id dst_origin_sid) FINAL OVERRIDE { const exploded_node *src_node = m_eedge.m_src; const program_point &src_point = src_node->get_point (); const int src_stack_depth = src_point.get_stack_depth (); const exploded_node *dst_node = m_eedge.m_dest; const gimple *stmt = src_point.get_stmt (); const supernode *supernode = src_point.get_supernode (); const program_state &dst_state = dst_node->get_state (); int stack_depth = src_stack_depth; if (m_eedge.m_sedge && m_eedge.m_sedge->m_kind == SUPEREDGE_CFG_EDGE) { supernode = src_point.get_supernode (); stmt = supernode->get_last_stmt (); stack_depth = src_stack_depth; } /* Bulletproofing for state changes at calls/returns; TODO: is there a better way? */ if (!stmt) return false; tree origin_rep = dst_state.get_representative_tree (dst_origin_sid); if (origin_rep == NULL_TREE) origin_rep = get_any_origin (stmt, dst_rep, dst_state); m_emission_path->add_event (new state_change_event (supernode, stmt, stack_depth, sm, dst_rep, src_sm_val, dst_sm_val, origin_rep, dst_state)); return false; } const exploded_edge &m_eedge; checker_path *m_emission_path; }; /* Compare SRC_STATE and DST_STATE (which use EXT_STATE), and call VISITOR's on_state_change for every sm-state change that occurs to a tree, and on_global_state_change for every global state change that occurs. This determines the state changes that ought to be reported to the user: a combination of the effects of changes to sm_state_map (which maps svalues to sm-states), and of region_model changes (which map trees to svalues). Bail out early and return true if any call to on_global_state_change or on_state_change returns true, otherwise return false. This is split out to make it easier to experiment with changes to exploded_node granularity (so that we can observe what state changes lead to state_change_events being emitted). */ bool for_each_state_change (const program_state &src_state, const program_state &dst_state, const extrinsic_state &ext_state, state_change_visitor *visitor) { gcc_assert (src_state.m_checker_states.length () == ext_state.get_num_checkers ()); gcc_assert (dst_state.m_checker_states.length () == ext_state.get_num_checkers ()); for (unsigned i = 0; i < ext_state.get_num_checkers (); i++) { const state_machine &sm = ext_state.get_sm (i); const sm_state_map &src_smap = *src_state.m_checker_states[i]; const sm_state_map &dst_smap = *dst_state.m_checker_states[i]; /* Add events for any global state changes. */ if (src_smap.get_global_state () != dst_smap.get_global_state ()) if (visitor->on_global_state_change (sm, src_smap.get_global_state (), dst_smap.get_global_state ())) return true; /* Add events for per-svalue state changes. */ for (sm_state_map::iterator_t iter = dst_smap.begin (); iter != dst_smap.end (); ++iter) { /* Ideally we'd directly compare the SM state between src state and dst state, but there's no guarantee that the IDs can be meaningfully compared. */ svalue_id dst_sid = (*iter).first; state_machine::state_t dst_sm_val = (*iter).second.m_state; auto_vec<path_var> dst_pvs; dst_state.m_region_model->get_path_vars_for_svalue (dst_sid, &dst_pvs); unsigned j; path_var *dst_pv; FOR_EACH_VEC_ELT (dst_pvs, j, dst_pv) { tree dst_rep = dst_pv->m_tree; gcc_assert (dst_rep); if (dst_pv->m_stack_depth >= src_state.m_region_model->get_stack_depth ()) continue; svalue_id src_sid = src_state.m_region_model->get_rvalue (*dst_pv, NULL); if (src_sid.null_p ()) continue; state_machine::state_t src_sm_val = src_smap.get_state (src_sid); if (dst_sm_val != src_sm_val) { svalue_id dst_origin_sid = (*iter).second.m_origin; if (visitor->on_state_change (sm, src_sm_val, dst_sm_val, dst_rep, dst_origin_sid)) return true; } } } } return false; } /* Subroutine of diagnostic_manager::build_emission_path. Add any events for EEDGE to EMISSION_PATH. */ void diagnostic_manager::add_events_for_eedge (const exploded_edge &eedge, const extrinsic_state &ext_state, checker_path *emission_path) const { const exploded_node *src_node = eedge.m_src; const program_point &src_point = src_node->get_point (); const exploded_node *dst_node = eedge.m_dest; const program_point &dst_point = dst_node->get_point (); const int dst_stack_depth = dst_point.get_stack_depth (); if (get_logger ()) { get_logger ()->start_log_line (); pretty_printer *pp = get_logger ()->get_printer (); pp_printf (pp, "EN %i -> EN %i: ", eedge.m_src->m_index, eedge.m_dest->m_index); src_point.print (pp, format (false)); pp_string (pp, "-> "); dst_point.print (pp, format (false)); get_logger ()->end_log_line (); } const program_state &src_state = src_node->get_state (); const program_state &dst_state = dst_node->get_state (); /* Add state change events for the states that have changed. We add these before events for superedges, so that if we have a state_change_event due to following an edge, we'll get this sequence of events: | if (!ptr) | ~ | | | (1) assuming 'ptr' is non-NULL (state_change_event) | (2) following 'false' branch... (start_cfg_edge_event) ... | do_something (ptr); | ~~~~~~~~~~~~~^~~~~ | | | (3) ...to here (end_cfg_edge_event). */ state_change_event_creator visitor (eedge, emission_path); for_each_state_change (src_state, dst_state, ext_state, &visitor); /* Allow non-standard edges to add events, e.g. when rewinding from longjmp to a setjmp. */ if (eedge.m_custom_info) eedge.m_custom_info->add_events_to_path (emission_path, eedge); /* Add events for superedges, function entries, and for statements. */ switch (dst_point.get_kind ()) { default: break; case PK_BEFORE_SUPERNODE: if (src_point.get_kind () == PK_AFTER_SUPERNODE) { if (eedge.m_sedge) add_events_for_superedge (eedge, emission_path); } /* Add function entry events. */ if (dst_point.get_supernode ()->entry_p ()) { emission_path->add_event (new function_entry_event (dst_point.get_supernode ()->get_start_location (), dst_point.get_fndecl (), dst_stack_depth)); } break; case PK_BEFORE_STMT: { const gimple *stmt = dst_point.get_stmt (); const gcall *call = dyn_cast <const gcall *> (stmt); if (call && is_setjmp_call_p (call)) emission_path->add_event (new setjmp_event (stmt->location, dst_node, dst_point.get_fndecl (), dst_stack_depth, call)); else emission_path->add_event (new statement_event (stmt, dst_point.get_fndecl (), dst_stack_depth, dst_state)); } break; } } /* Subroutine of diagnostic_manager::add_events_for_eedge where EEDGE has an underlying superedge i.e. a CFG edge, or an interprocedural call/return. Add any events for the superedge to EMISSION_PATH. */ void diagnostic_manager::add_events_for_superedge (const exploded_edge &eedge, checker_path *emission_path) const { gcc_assert (eedge.m_sedge); const exploded_node *src_node = eedge.m_src; const program_point &src_point = src_node->get_point (); const exploded_node *dst_node = eedge.m_dest; const program_point &dst_point = dst_node->get_point (); const int src_stack_depth = src_point.get_stack_depth (); const int dst_stack_depth = dst_point.get_stack_depth (); const gimple *last_stmt = src_point.get_supernode ()->get_last_stmt (); switch (eedge.m_sedge->m_kind) { case SUPEREDGE_CFG_EDGE: { emission_path->add_event (new start_cfg_edge_event (eedge, (last_stmt ? last_stmt->location : UNKNOWN_LOCATION), src_point.get_fndecl (), src_stack_depth)); emission_path->add_event (new end_cfg_edge_event (eedge, dst_point.get_supernode ()->get_start_location (), dst_point.get_fndecl (), dst_stack_depth)); } break; case SUPEREDGE_CALL: { emission_path->add_event (new call_event (eedge, (last_stmt ? last_stmt->location : UNKNOWN_LOCATION), src_point.get_fndecl (), src_stack_depth)); } break; case SUPEREDGE_INTRAPROCEDURAL_CALL: { /* TODO: add a subclass for this, or generate events for the summary. */ emission_path->add_event (new debug_event ((last_stmt ? last_stmt->location : UNKNOWN_LOCATION), src_point.get_fndecl (), src_stack_depth, "call summary")); } break; case SUPEREDGE_RETURN: { const return_superedge *return_edge = as_a <const return_superedge *> (eedge.m_sedge); const gcall *call_stmt = return_edge->get_call_stmt (); emission_path->add_event (new return_event (eedge, (call_stmt ? call_stmt->location : UNKNOWN_LOCATION), dst_point.get_fndecl (), dst_stack_depth)); } break; } } /* Prune PATH, based on the verbosity level, to the most pertinent events for a diagnostic that involves VAR ending in state STATE (for state machine SM). PATH is updated in place, and the redundant checker_events are deleted. As well as deleting events, call record_critical_state on events in which state critical to the pending_diagnostic is being handled; see the comment for diagnostic_manager::prune_for_sm_diagnostic. */ void diagnostic_manager::prune_path (checker_path *path, const state_machine *sm, tree var, state_machine::state_t state) const { LOG_FUNC (get_logger ()); path->maybe_log (get_logger (), "path"); prune_for_sm_diagnostic (path, sm, var, state); prune_interproc_events (path); finish_pruning (path); path->maybe_log (get_logger (), "pruned"); } /* First pass of diagnostic_manager::prune_path: apply verbosity level, pruning unrelated state change events. Iterate backwards through PATH, skipping state change events that aren't VAR but update the pertinent VAR when state-copying occurs. As well as deleting events, call record_critical_state on events in which state critical to the pending_diagnostic is being handled, so that the event's get_desc vfunc can potentially supply a more precise description of the event to the user. e.g. improving "calling 'foo' from 'bar'" to "passing possibly-NULL pointer 'ptr' to 'foo' from 'bar' as param 1" when the diagnostic relates to later dereferencing 'ptr'. */ void diagnostic_manager::prune_for_sm_diagnostic (checker_path *path, const state_machine *sm, tree var, state_machine::state_t state) const { /* If we have a constant (such as NULL), assume its state is also constant, so as not to attempt to get its lvalue whilst tracking the origin of the state. */ if (var && CONSTANT_CLASS_P (var)) var = NULL_TREE; int idx = path->num_events () - 1; while (idx >= 0 && idx < (signed)path->num_events ()) { checker_event *base_event = path->get_checker_event (idx); if (get_logger ()) { if (sm) { if (var) log ("considering event %i, with var: %qE, state: %qs", idx, var, sm->get_state_name (state)); else log ("considering event %i, with global state: %qs", idx, sm->get_state_name (state)); } else log ("considering event %i", idx); } switch (base_event->m_kind) { default: gcc_unreachable (); case EK_DEBUG: if (m_verbosity < 3) { log ("filtering event %i: debug event", idx); path->delete_event (idx); } break; case EK_CUSTOM: /* Don't filter custom events. */ break; case EK_STMT: { /* If this stmt is the origin of "var", update var. */ if (var) { statement_event *stmt_event = (statement_event *)base_event; tree new_var = get_any_origin (stmt_event->m_stmt, var, stmt_event->m_dst_state); if (new_var) { log ("event %i: switching var of interest from %qE to %qE", idx, var, new_var); var = new_var; } } if (m_verbosity < 3) { log ("filtering event %i: statement event", idx); path->delete_event (idx); } } break; case EK_FUNCTION_ENTRY: if (m_verbosity < 1) { log ("filtering event %i: function entry", idx); path->delete_event (idx); } break; case EK_STATE_CHANGE: { state_change_event *state_change = (state_change_event *)base_event; if (state_change->get_lvalue (state_change->m_var) == state_change->get_lvalue (var)) { if (state_change->m_origin) { log ("event %i: switching var of interest from %qE to %qE", idx, var, state_change->m_origin); var = state_change->m_origin; } log ("event %i: switching state of interest from %qs to %qs", idx, sm->get_state_name (state_change->m_to), sm->get_state_name (state_change->m_from)); state = state_change->m_from; } else if (m_verbosity < 3) { if (var) log ("filtering event %i:" " state change to %qE unrelated to %qE", idx, state_change->m_var, var); else log ("filtering event %i: state change to %qE", idx, state_change->m_var); path->delete_event (idx); } } break; case EK_START_CFG_EDGE: { cfg_edge_event *event = (cfg_edge_event *)base_event; const cfg_superedge& cfg_superedge = event->get_cfg_superedge (); const supernode *dest = event->m_sedge->m_dest; /* Do we have an SSA_NAME defined via a phi node in the dest CFG node? */ if (var && TREE_CODE (var) == SSA_NAME) if (SSA_NAME_DEF_STMT (var)->bb == dest->m_bb) { if (gphi *phi = dyn_cast <gphi *> (SSA_NAME_DEF_STMT (var))) { /* Update var based on its phi node. */ tree old_var = var; var = cfg_superedge.get_phi_arg (phi); log ("updating from %qE to %qE based on phi node", old_var, var); if (get_logger ()) { pretty_printer pp; pp_gimple_stmt_1 (&pp, phi, 0, (dump_flags_t)0); log (" phi: %s", pp_formatted_text (&pp)); } /* If we've chosen a bad exploded_path, then the phi arg might be a constant. Fail gracefully for this case. */ if (CONSTANT_CLASS_P (var)) { log ("new var is a constant (bad path?);" " setting var to NULL"); var = NULL; } } } /* TODO: is this edge significant to var? See if var can be in other states in the dest, but not in other states in the src? Must have multiple sibling edges. */ if (event->should_filter_p (m_verbosity)) { log ("filtering event %i: CFG edge", idx); path->delete_event (idx); /* Also delete the corresponding EK_END_CFG_EDGE. */ gcc_assert (path->get_checker_event (idx)->m_kind == EK_END_CFG_EDGE); path->delete_event (idx); } } break; case EK_END_CFG_EDGE: /* These come in pairs with EK_START_CFG_EDGE events and are filtered when their start event is filtered. */ break; case EK_CALL_EDGE: { call_event *event = (call_event *)base_event; const callgraph_superedge& cg_superedge = event->get_callgraph_superedge (); callsite_expr expr; tree caller_var = cg_superedge.map_expr_from_callee_to_caller (var, &expr); if (caller_var) { log ("event %i:" " switching var of interest" " from %qE in callee to %qE in caller", idx, var, caller_var); var = caller_var; if (expr.param_p ()) event->record_critical_state (var, state); } } break; case EK_RETURN_EDGE: // TODO: potentially update var/state based on return value, // args etc { if (var) { return_event *event = (return_event *)base_event; const callgraph_superedge& cg_superedge = event->get_callgraph_superedge (); callsite_expr expr; tree callee_var = cg_superedge.map_expr_from_caller_to_callee (var, &expr); if (callee_var) { log ("event %i:" " switching var of interest" " from %qE in caller to %qE in callee", idx, var, callee_var); var = callee_var; if (expr.return_value_p ()) event->record_critical_state (var, state); } } } break; case EK_SETJMP: /* TODO: only show setjmp_events that matter i.e. those for which there is a later rewind event using them. */ case EK_REWIND_FROM_LONGJMP: case EK_REWIND_TO_SETJMP: break; case EK_WARNING: /* Always show the final "warning" event in the path. */ break; } idx--; } } /* Second pass of diagnostic_manager::prune_path: remove redundant interprocedural information. For example, given: (1)- calling "f2" from "f1" (2)--- entry to "f2" (3)--- calling "f3" from "f2" (4)----- entry to "f3" (5)--- returning to "f2" to "f3" (6)- returning to "f1" to "f2" with no other intervening events, then none of these events are likely to be interesting to the user. Prune [..., call, function-entry, return, ...] triples repeatedly until nothing has changed. For the example above, this would remove events (3, 4, 5), and then remove events (1, 2, 6). */ void diagnostic_manager::prune_interproc_events (checker_path *path) const { bool changed = false; do { changed = false; int idx = path->num_events () - 1; while (idx >= 0) { /* Prune [..., call, function-entry, return, ...] triples. */ if (idx + 2 < (signed)path->num_events () && path->get_checker_event (idx)->is_call_p () && path->get_checker_event (idx + 1)->is_function_entry_p () && path->get_checker_event (idx + 2)->is_return_p ()) { if (get_logger ()) { label_text desc (path->get_checker_event (idx)->get_desc (false)); log ("filtering events %i-%i:" " irrelevant call/entry/return: %s", idx, idx + 2, desc.m_buffer); desc.maybe_free (); } path->delete_event (idx + 2); path->delete_event (idx + 1); path->delete_event (idx); changed = true; idx--; continue; } /* Prune [..., call, return, ...] pairs (for -fanalyzer-verbosity=0). */ if (idx + 1 < (signed)path->num_events () && path->get_checker_event (idx)->is_call_p () && path->get_checker_event (idx + 1)->is_return_p ()) { if (get_logger ()) { label_text desc (path->get_checker_event (idx)->get_desc (false)); log ("filtering events %i-%i:" " irrelevant call/return: %s", idx, idx + 1, desc.m_buffer); desc.maybe_free (); } path->delete_event (idx + 1); path->delete_event (idx); changed = true; idx--; continue; } idx--; } } while (changed); } /* Final pass of diagnostic_manager::prune_path. If all we're left with is in one function, then filter function entry events. */ void diagnostic_manager::finish_pruning (checker_path *path) const { if (!path->interprocedural_p ()) { int idx = path->num_events () - 1; while (idx >= 0 && idx < (signed)path->num_events ()) { checker_event *base_event = path->get_checker_event (idx); if (base_event->m_kind == EK_FUNCTION_ENTRY) { log ("filtering event %i:" " function entry for purely intraprocedural path", idx); path->delete_event (idx); } idx--; } } } } // namespace ana #endif /* #if ENABLE_ANALYZER */