|  | /* Subclasses of diagnostics::paths::event for analyzer diagnostics. | 
|  | Copyright (C) 2019-2025 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 "analyzer/common.h" | 
|  |  | 
|  | #include "gimple-pretty-print.h" | 
|  | #include "sbitmap.h" | 
|  | #include "ordered-hash-map.h" | 
|  | #include "fold-const.h" | 
|  | #include "gimple-iterator.h" | 
|  | #include "inlining-iterator.h" | 
|  | #include "tree-logical-location.h" | 
|  | #include "diagnostics/sarif-sink.h" | 
|  | #include "diagnostics/state-graphs.h" | 
|  |  | 
|  | #include "analyzer/analyzer-logging.h" | 
|  | #include "analyzer/sm.h" | 
|  | #include "analyzer/call-string.h" | 
|  | #include "analyzer/program-point.h" | 
|  | #include "analyzer/store.h" | 
|  | #include "analyzer/region-model.h" | 
|  | #include "analyzer/program-state.h" | 
|  | #include "analyzer/checker-path.h" | 
|  | #include "analyzer/supergraph.h" | 
|  | #include "analyzer/pending-diagnostic.h" | 
|  | #include "analyzer/diagnostic-manager.h" | 
|  | #include "analyzer/constraint-manager.h" | 
|  | #include "analyzer/checker-event.h" | 
|  | #include "analyzer/exploded-graph.h" | 
|  |  | 
|  | #if ENABLE_ANALYZER | 
|  |  | 
|  | namespace ana { | 
|  |  | 
|  | /* Get a string for EK.  */ | 
|  |  | 
|  | const char * | 
|  | event_kind_to_string (enum event_kind ek) | 
|  | { | 
|  | switch (ek) | 
|  | { | 
|  | default: | 
|  | gcc_unreachable (); | 
|  | case event_kind::debug: | 
|  | return "debug"; | 
|  | case event_kind::custom: | 
|  | return "custom"; | 
|  | case event_kind::stmt: | 
|  | return "stmt"; | 
|  | case event_kind::region_creation: | 
|  | return "region_creation"; | 
|  | case event_kind::function_entry: | 
|  | return "function_entry"; | 
|  | case event_kind::state_change: | 
|  | return "state_change"; | 
|  | case event_kind::start_cfg_edge: | 
|  | return "start_cfg_edge"; | 
|  | case event_kind::end_cfg_edge: | 
|  | return "end_cfg_edge"; | 
|  | case event_kind::catch_: | 
|  | return "catch"; | 
|  | case event_kind::call_edge: | 
|  | return "call_edge"; | 
|  | case event_kind::return_edge: | 
|  | return "return_edge"; | 
|  | case event_kind::start_consolidated_cfg_edges: | 
|  | return "start_consolidated_cfg_edges"; | 
|  | case event_kind::end_consolidated_cfg_edges: | 
|  | return "end_consolidated_cfg_edges"; | 
|  | case event_kind::inlined_call: | 
|  | return "inlined_call"; | 
|  | case event_kind::setjmp_: | 
|  | return "setjmp"; | 
|  | case event_kind::rewind_from_longjmp: | 
|  | return "rewind_from_longjmp"; | 
|  | case event_kind::rewind_to_setjmp: | 
|  | return "rewind_to_setjmp"; | 
|  | case event_kind::throw_: | 
|  | return "throw"; | 
|  | case event_kind::unwind: | 
|  | return "unwind"; | 
|  | case event_kind::warning: | 
|  | return "warning"; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* class checker_event : public diagnostics::paths::event.  */ | 
|  |  | 
|  | /* checker_event's ctor.  */ | 
|  |  | 
|  | checker_event::checker_event (enum event_kind kind, | 
|  | const event_loc_info &loc_info) | 
|  | : m_path (nullptr), | 
|  | m_kind (kind), m_loc (loc_info.m_loc), | 
|  | m_original_fndecl (loc_info.m_fndecl), | 
|  | m_effective_fndecl (loc_info.m_fndecl), | 
|  | m_original_depth (loc_info.m_depth), | 
|  | m_effective_depth (loc_info.m_depth), | 
|  | m_pending_diagnostic (nullptr), m_emission_id (), | 
|  | m_logical_loc | 
|  | (tree_logical_location_manager::key_from_tree (loc_info.m_fndecl)) | 
|  | { | 
|  | /* Update effective fndecl and depth if inlining has been recorded.  */ | 
|  | if (flag_analyzer_undo_inlining) | 
|  | { | 
|  | inlining_info info (m_loc); | 
|  | if (info.get_inner_fndecl ()) | 
|  | { | 
|  | m_effective_fndecl = info.get_inner_fndecl (); | 
|  | m_effective_depth += info.get_extra_frames (); | 
|  | m_logical_loc | 
|  | = tree_logical_location_manager::key_from_tree (m_effective_fndecl); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /* No-op implementation of diagnostics::paths::event::get_meaning vfunc for | 
|  | checker_event: checker events have no meaning by default.  */ | 
|  |  | 
|  | diagnostics::paths::event::meaning | 
|  | checker_event::get_meaning () const | 
|  | { | 
|  | return diagnostics::paths::event::meaning (); | 
|  | } | 
|  |  | 
|  | /* Implementation of diagnostics::paths::event::maybe_add_sarif_properties | 
|  | for checker_event.  */ | 
|  |  | 
|  | void | 
|  | checker_event:: | 
|  | maybe_add_sarif_properties (diagnostics::sarif_builder &builder, | 
|  | diagnostics::sarif_object &thread_flow_loc_obj) const | 
|  | { | 
|  | auto &props = thread_flow_loc_obj.get_or_create_properties (); | 
|  | #define PROPERTY_PREFIX "gcc/analyzer/checker_event/" | 
|  | props.set (PROPERTY_PREFIX "emission_id", | 
|  | diagnostic_event_id_to_json  (m_emission_id)); | 
|  | props.set_string (PROPERTY_PREFIX "kind", event_kind_to_string (m_kind)); | 
|  |  | 
|  | if (m_original_fndecl != m_effective_fndecl) | 
|  | props.set_logical_location | 
|  | (PROPERTY_PREFIX "original_fndecl", | 
|  | builder, | 
|  | tree_logical_location_manager::key_from_tree (m_original_fndecl)); | 
|  |  | 
|  | if (m_original_depth != m_effective_depth) | 
|  | props.set_integer (PROPERTY_PREFIX "original_depth", m_original_depth); | 
|  | #undef PROPERTY_PREFIX | 
|  | } | 
|  |  | 
|  | /* Dump this event to PP (for debugging/logging purposes).  */ | 
|  |  | 
|  | void | 
|  | checker_event::dump (pretty_printer *pp) const | 
|  | { | 
|  | pp_character (pp, '"'); | 
|  | print_desc (*pp); | 
|  | pp_printf (pp, "\" (depth %i", m_effective_depth); | 
|  |  | 
|  | if (m_effective_depth != m_original_depth) | 
|  | pp_printf (pp, " corrected from %i", | 
|  | m_original_depth); | 
|  | if (m_effective_fndecl) | 
|  | { | 
|  | pp_printf (pp, ", fndecl %qE", m_effective_fndecl); | 
|  | if (m_effective_fndecl != m_original_fndecl) | 
|  | pp_printf (pp, " corrected from %qE", m_original_fndecl); | 
|  | } | 
|  | pp_printf (pp, ", m_loc=%llx)", | 
|  | (unsigned long long) get_location ()); | 
|  | } | 
|  |  | 
|  | /* Dump this event to stderr (for debugging/logging purposes).  */ | 
|  |  | 
|  | DEBUG_FUNCTION void | 
|  | checker_event::debug () const | 
|  | { | 
|  | tree_dump_pretty_printer pp (stderr); | 
|  | dump (&pp); | 
|  | pp_newline (&pp); | 
|  | } | 
|  |  | 
|  | /* Hook for being notified when this event has its final id EMISSION_ID | 
|  | and is about to emitted for PD. | 
|  |  | 
|  | Base implementation of checker_event::prepare_for_emission vfunc; | 
|  | subclasses that override this should chain up to it. | 
|  |  | 
|  | Record PD and EMISSION_ID, and call the print_desc vfunc, so that any | 
|  | side-effects of the call to print_desc take place before | 
|  | pending_diagnostic::emit is called. | 
|  |  | 
|  | For example, state_change_event::print_desc can call | 
|  | pending_diagnostic::describe_state_change; free_of_non_heap can use this | 
|  | to tweak the message (TODO: would be neater to simply capture the | 
|  | pertinent data within the sm-state).  */ | 
|  |  | 
|  | void | 
|  | checker_event::prepare_for_emission (checker_path *path, | 
|  | pending_diagnostic *pd, | 
|  | diagnostics::paths::event_id_t emission_id) | 
|  | { | 
|  | m_path = path; | 
|  | m_pending_diagnostic = pd; | 
|  | m_emission_id = emission_id; | 
|  |  | 
|  | auto pp = global_dc->clone_printer (); | 
|  | print_desc (*pp.get ()); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<diagnostics::digraphs::digraph> | 
|  | checker_event::maybe_make_diagnostic_state_graph (bool debug) const | 
|  | { | 
|  | const program_state *state = get_program_state (); | 
|  | if (!state) | 
|  | return nullptr; | 
|  |  | 
|  | gcc_assert (m_path); | 
|  | const extrinsic_state &ext_state = m_path->get_ext_state (); | 
|  |  | 
|  | auto result = state->make_diagnostic_state_graph (ext_state); | 
|  |  | 
|  | if (debug) | 
|  | { | 
|  | pretty_printer pp; | 
|  | text_art::theme *theme = global_dc->get_diagram_theme (); | 
|  | text_art::dump_to_pp (*state, theme, &pp); | 
|  | result->set_attr (STATE_GRAPH_PREFIX, | 
|  | "analyzer/program_state/", | 
|  | pp_formatted_text (&pp)); | 
|  | } | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /* class debug_event : public checker_event.  */ | 
|  |  | 
|  | /* Implementation of diagnostics::paths::event::print_desc vfunc for | 
|  | debug_event. | 
|  | Use the saved string as the event's description.  */ | 
|  |  | 
|  | void | 
|  | debug_event::print_desc (pretty_printer &pp) const | 
|  | { | 
|  | pp_string (&pp, m_desc); | 
|  | } | 
|  |  | 
|  | /* class precanned_custom_event : public custom_event.  */ | 
|  |  | 
|  | /* Implementation of diagnostics::paths::event::print_desc vfunc for | 
|  | precanned_custom_event. | 
|  | Use the saved string as the event's description.  */ | 
|  |  | 
|  | void | 
|  | precanned_custom_event::print_desc (pretty_printer &pp) const | 
|  | { | 
|  | pp_string (&pp, m_desc); | 
|  | } | 
|  |  | 
|  | /* class statement_event : public checker_event.  */ | 
|  |  | 
|  | /* statement_event's ctor.  */ | 
|  |  | 
|  | statement_event::statement_event (const gimple *stmt, tree fndecl, int depth, | 
|  | const program_state &dst_state) | 
|  | : checker_event (event_kind::stmt, | 
|  | event_loc_info (gimple_location (stmt), fndecl, depth)), | 
|  | m_stmt (stmt), | 
|  | m_dst_state (dst_state) | 
|  | { | 
|  | } | 
|  |  | 
|  | /* Implementation of diagnostics::paths::event::print_desc vfunc for | 
|  | statement_event. | 
|  | Use the statement's dump form as the event's description.  */ | 
|  |  | 
|  | void | 
|  | statement_event::print_desc (pretty_printer &pp) const | 
|  | { | 
|  | pp_string (&pp, "stmt: "); | 
|  | pp_gimple_stmt_1 (&pp, m_stmt, 0, (dump_flags_t)0); | 
|  | } | 
|  |  | 
|  | /* class region_creation_event : public checker_event.  */ | 
|  |  | 
|  | region_creation_event::region_creation_event (const event_loc_info &loc_info) | 
|  | : checker_event (event_kind::region_creation, loc_info) | 
|  | { | 
|  | } | 
|  |  | 
|  | /* The various region_creation_event subclasses' print_desc | 
|  | implementations.  */ | 
|  |  | 
|  | void | 
|  | region_creation_event_memory_space::print_desc (pretty_printer &pp) const | 
|  | { | 
|  | switch (m_mem_space) | 
|  | { | 
|  | default: | 
|  | pp_string (&pp, "region created here"); | 
|  | return; | 
|  | case MEMSPACE_STACK: | 
|  | pp_string (&pp, "region created on stack here"); | 
|  | return; | 
|  | case MEMSPACE_HEAP: | 
|  | pp_string (&pp, "region created on heap here"); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | void | 
|  | region_creation_event_capacity::print_desc (pretty_printer &pp) const | 
|  | { | 
|  | gcc_assert (m_capacity); | 
|  | if (TREE_CODE (m_capacity) == INTEGER_CST) | 
|  | { | 
|  | unsigned HOST_WIDE_INT hwi = tree_to_uhwi (m_capacity); | 
|  | return pp_printf_n (&pp, | 
|  | hwi, | 
|  | "capacity: %wu byte", | 
|  | "capacity: %wu bytes", | 
|  | hwi); | 
|  | } | 
|  | else | 
|  | return pp_printf (&pp, "capacity: %qE bytes", m_capacity); | 
|  | } | 
|  |  | 
|  | void | 
|  | region_creation_event_allocation_size::print_desc (pretty_printer &pp) const | 
|  | { | 
|  | if (m_capacity) | 
|  | { | 
|  | if (TREE_CODE (m_capacity) == INTEGER_CST) | 
|  | pp_printf_n (&pp, | 
|  | tree_to_uhwi (m_capacity), | 
|  | "allocated %E byte here", | 
|  | "allocated %E bytes here", | 
|  | m_capacity); | 
|  | else | 
|  | pp_printf (&pp, | 
|  | "allocated %qE bytes here", | 
|  | m_capacity); | 
|  | } | 
|  | pp_printf (&pp, "allocated here"); | 
|  | } | 
|  |  | 
|  | void | 
|  | region_creation_event_debug::print_desc (pretty_printer &pp) const | 
|  | { | 
|  | pp_string (&pp, "region creation: "); | 
|  | m_reg->dump_to_pp (&pp, true); | 
|  | if (m_capacity) | 
|  | pp_printf (&pp, " capacity: %qE", m_capacity); | 
|  | } | 
|  |  | 
|  | /* class function_entry_event : public checker_event.  */ | 
|  |  | 
|  | function_entry_event::function_entry_event (const program_point &dst_point, | 
|  | const program_state &state) | 
|  | : checker_event (event_kind::function_entry, | 
|  | event_loc_info (dst_point.get_supernode | 
|  | ()->get_start_location (), | 
|  | dst_point.get_fndecl (), | 
|  | dst_point.get_stack_depth ())), | 
|  | m_state (state) | 
|  | { | 
|  | } | 
|  |  | 
|  | /* Implementation of diagnostics::paths::event::print_desc vfunc for | 
|  | function_entry_event. | 
|  |  | 
|  | Use a string such as "entry to 'foo'" as the event's description.  */ | 
|  |  | 
|  | void | 
|  | function_entry_event::print_desc (pretty_printer &pp) const | 
|  | { | 
|  | pp_printf (&pp, "entry to %qE", m_effective_fndecl); | 
|  | } | 
|  |  | 
|  | /* Implementation of diagnostics::paths::event::get_meaning vfunc for | 
|  | function entry.  */ | 
|  |  | 
|  | diagnostics::paths::event::meaning | 
|  | function_entry_event::get_meaning () const | 
|  | { | 
|  | return meaning (verb::enter, noun::function); | 
|  | } | 
|  |  | 
|  | /* class state_change_event : public checker_event.  */ | 
|  |  | 
|  | /* state_change_event's ctor.  */ | 
|  |  | 
|  | state_change_event::state_change_event (const supernode *node, | 
|  | const gimple *stmt, | 
|  | int stack_depth, | 
|  | const state_machine &sm, | 
|  | const svalue *sval, | 
|  | state_machine::state_t from, | 
|  | state_machine::state_t to, | 
|  | const svalue *origin, | 
|  | const program_state &dst_state, | 
|  | const exploded_node *enode) | 
|  | : checker_event (event_kind::state_change, | 
|  | event_loc_info (stmt->location, | 
|  | node->m_fun->decl, | 
|  | stack_depth)), | 
|  | m_node (node), m_stmt (stmt), m_sm (sm), | 
|  | m_sval (sval), m_from (from), m_to (to), | 
|  | m_origin (origin), | 
|  | m_dst_state (dst_state), | 
|  | m_enode (enode) | 
|  | { | 
|  | } | 
|  |  | 
|  | /* Implementation of diagnostics::paths::event::print_desc vfunc for | 
|  | state_change_event. | 
|  |  | 
|  | Attempt to generate a nicer human-readable description. | 
|  | For greatest precision-of-wording, give the pending diagnostic | 
|  | a chance to describe this state change (in terms of the | 
|  | diagnostic). | 
|  | Note that we only have a pending_diagnostic set on the event once | 
|  | the diagnostic is about to being emitted, so the description for | 
|  | an event can change.  */ | 
|  |  | 
|  | void | 
|  | state_change_event::print_desc (pretty_printer &pp) const | 
|  | { | 
|  | if (m_pending_diagnostic) | 
|  | { | 
|  | region_model *model = m_dst_state.m_region_model; | 
|  | tree var = model->get_representative_tree (m_sval); | 
|  | tree origin = model->get_representative_tree (m_origin); | 
|  | evdesc::state_change evd (var, origin, | 
|  | m_from, m_to, m_emission_id, *this); | 
|  | if (m_pending_diagnostic->describe_state_change (pp, evd)) | 
|  | { | 
|  | if (flag_analyzer_verbose_state_changes) | 
|  | { | 
|  | /* Append debugging information about this event.  */ | 
|  |  | 
|  | if (var) | 
|  | pp_printf (&pp, " (state of %qE: ", var); | 
|  | else | 
|  | pp_string (&pp, " (state: "); | 
|  |  | 
|  | pp_printf (&pp, "%qs -> %qs, ", | 
|  | m_from->get_name (), | 
|  | m_to->get_name ()); | 
|  |  | 
|  | if (m_origin) | 
|  | pp_printf (&pp, "origin: %qE", origin); | 
|  | else | 
|  | pp_string (&pp, "NULL origin"); | 
|  |  | 
|  | /* Get any "meaning" of event.  */ | 
|  | diagnostics::paths::event::meaning meaning = get_meaning (); | 
|  | pp_string (&pp, ", meaning: "); | 
|  | meaning.dump_to_pp (&pp); | 
|  | pp_string (&pp, ")"); | 
|  | } | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Fallback description.  */ | 
|  | if (m_sval) | 
|  | { | 
|  | label_text sval_desc = m_sval->get_desc (); | 
|  | pp_printf (&pp, | 
|  | "state of %qs: %qs -> %qs", | 
|  | sval_desc.get (), | 
|  | m_from->get_name (), | 
|  | m_to->get_name ()); | 
|  | if (m_origin) | 
|  | { | 
|  | label_text origin_desc = m_origin->get_desc (); | 
|  | pp_printf (&pp, " (origin: %qs)", | 
|  | origin_desc.get ()); | 
|  | } | 
|  | else | 
|  | pp_string (&pp, " (NULL origin)"); | 
|  | } | 
|  | else | 
|  | { | 
|  | gcc_assert (m_origin == nullptr); | 
|  | pp_printf (&pp, | 
|  | "global state: %qs -> %qs", | 
|  | m_from->get_name (), | 
|  | m_to->get_name ()); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Implementation of diagnostics::paths::event::get_meaning vfunc for | 
|  | state change events: delegate to the pending_diagnostic to | 
|  | get any meaning.  */ | 
|  |  | 
|  | diagnostics::paths::event::meaning | 
|  | state_change_event::get_meaning () const | 
|  | { | 
|  | if (m_pending_diagnostic) | 
|  | { | 
|  | region_model *model = m_dst_state.m_region_model; | 
|  | tree var = model->get_representative_tree (m_sval); | 
|  | tree origin = model->get_representative_tree (m_origin); | 
|  | evdesc::state_change evd (var, origin, | 
|  | m_from, m_to, m_emission_id, *this); | 
|  | return m_pending_diagnostic->get_meaning_for_state_change (evd); | 
|  | } | 
|  | else | 
|  | return meaning (); | 
|  | } | 
|  |  | 
|  | /* class superedge_event : public checker_event.  */ | 
|  |  | 
|  | /* Implementation of diagnostics::paths::event::maybe_add_sarif_properties | 
|  | for superedge_event.  */ | 
|  |  | 
|  | void | 
|  | superedge_event:: | 
|  | maybe_add_sarif_properties (diagnostics::sarif_builder &builder, | 
|  | diagnostics::sarif_object &thread_flow_loc_obj) | 
|  | const | 
|  | { | 
|  | checker_event::maybe_add_sarif_properties (builder, thread_flow_loc_obj); | 
|  | auto &props = thread_flow_loc_obj.get_or_create_properties (); | 
|  | #define PROPERTY_PREFIX "gcc/analyzer/superedge_event/" | 
|  | if (m_sedge) | 
|  | props.set (PROPERTY_PREFIX "superedge", m_sedge->to_json ()); | 
|  | #undef PROPERTY_PREFIX | 
|  | } | 
|  |  | 
|  | /* Get the callgraph_superedge for this superedge_event, which must be | 
|  | for an interprocedural edge, rather than a CFG edge.  */ | 
|  |  | 
|  | const callgraph_superedge& | 
|  | superedge_event::get_callgraph_superedge () const | 
|  | { | 
|  | gcc_assert (m_sedge->m_kind != SUPEREDGE_CFG_EDGE); | 
|  | return *m_sedge->dyn_cast_callgraph_superedge (); | 
|  | } | 
|  |  | 
|  | /* Determine if this event should be filtered at the given verbosity | 
|  | level.  */ | 
|  |  | 
|  | bool | 
|  | superedge_event::should_filter_p (int verbosity) const | 
|  | { | 
|  | switch (m_sedge->m_kind) | 
|  | { | 
|  | case SUPEREDGE_CFG_EDGE: | 
|  | { | 
|  | if (verbosity < 2) | 
|  | return true; | 
|  |  | 
|  | if (verbosity < 4) | 
|  | { | 
|  | /* Filter events with empty descriptions.  This ought to filter | 
|  | FALLTHRU, but retain true/false/switch edges.  */ | 
|  | auto pp = global_dc->clone_printer (); | 
|  | print_desc (*pp.get ()); | 
|  | if (pp_formatted_text (pp.get ()) [0] == '\0') | 
|  | return true; | 
|  | } | 
|  | } | 
|  | break; | 
|  |  | 
|  | default: | 
|  | break; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | const program_state * | 
|  | superedge_event::get_program_state () const | 
|  | { | 
|  | return &m_eedge.m_dest->get_state (); | 
|  | } | 
|  |  | 
|  | /* superedge_event's ctor.  */ | 
|  |  | 
|  | superedge_event::superedge_event (enum event_kind kind, | 
|  | const exploded_edge &eedge, | 
|  | const event_loc_info &loc_info) | 
|  | : checker_event (kind, loc_info), | 
|  | m_eedge (eedge), m_sedge (eedge.m_sedge), | 
|  | m_var (NULL_TREE), m_critical_state (0) | 
|  | { | 
|  | /* Note that m_sedge can be nullptr for e.g. jumps through | 
|  | function pointers.  */ | 
|  | } | 
|  |  | 
|  | /* class cfg_edge_event : public superedge_event.  */ | 
|  |  | 
|  | /* Get the cfg_superedge for this cfg_edge_event.  */ | 
|  |  | 
|  | const cfg_superedge & | 
|  | cfg_edge_event::get_cfg_superedge () const | 
|  | { | 
|  | return *m_sedge->dyn_cast_cfg_superedge (); | 
|  | } | 
|  |  | 
|  | /* cfg_edge_event's ctor.  */ | 
|  |  | 
|  | cfg_edge_event::cfg_edge_event (enum event_kind kind, | 
|  | const exploded_edge &eedge, | 
|  | const event_loc_info &loc_info) | 
|  | : superedge_event (kind, eedge, loc_info) | 
|  | { | 
|  | gcc_assert (eedge.m_sedge->m_kind == SUPEREDGE_CFG_EDGE); | 
|  | } | 
|  |  | 
|  | /* Implementation of diagnostics::paths::event::get_meaning vfunc for | 
|  | CFG edge events.  */ | 
|  |  | 
|  | diagnostics::paths::event::meaning | 
|  | cfg_edge_event::get_meaning () const | 
|  | { | 
|  | const cfg_superedge& cfg_sedge = get_cfg_superedge (); | 
|  | if (cfg_sedge.true_value_p ()) | 
|  | return meaning (verb::branch, property::true_); | 
|  | else if (cfg_sedge.false_value_p ()) | 
|  | return meaning (verb::branch, property::false_); | 
|  | else | 
|  | return meaning (); | 
|  | } | 
|  |  | 
|  | /* class start_cfg_edge_event : public cfg_edge_event.  */ | 
|  |  | 
|  | /* Implementation of diagnostics::paths::event::print_desc vfunc for | 
|  | start_cfg_edge_event. | 
|  |  | 
|  | If -fanalyzer-verbose-edges, then generate low-level descriptions, such | 
|  | as | 
|  | "taking 'true' edge SN:7 -> SN:8". | 
|  |  | 
|  | Otherwise, generate strings using the label of the underlying CFG if | 
|  | any, such as: | 
|  | "following 'true' branch..." or | 
|  | "following 'case 3' branch..." | 
|  | "following 'default' branch..." | 
|  |  | 
|  | For conditionals, attempt to supply a description of the condition that | 
|  | holds, such as: | 
|  | "following 'false' branch (when 'ptr' is non-NULL)..." | 
|  |  | 
|  | Failing that, print nothing (which will lead to this event | 
|  | being filtered).  */ | 
|  |  | 
|  | void | 
|  | start_cfg_edge_event::print_desc (pretty_printer &pp) const | 
|  | { | 
|  | bool user_facing = !flag_analyzer_verbose_edges; | 
|  | label_text edge_desc (m_sedge->get_description (user_facing)); | 
|  | if (user_facing) | 
|  | { | 
|  | if (edge_desc.get () && strlen (edge_desc.get ()) > 0) | 
|  | { | 
|  | label_text cond_desc = maybe_describe_condition (pp_show_color (&pp)); | 
|  | label_text result; | 
|  | if (cond_desc.get ()) | 
|  | pp_printf (&pp, | 
|  | "following %qs branch (%s)...", | 
|  | edge_desc.get (), cond_desc.get ()); | 
|  | else | 
|  | pp_printf (&pp, | 
|  | "following %qs branch...", | 
|  | edge_desc.get ()); | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | if (strlen (edge_desc.get ()) > 0) | 
|  | return pp_printf (&pp, | 
|  | "taking %qs edge SN:%i -> SN:%i", | 
|  | edge_desc.get (), | 
|  | m_sedge->m_src->m_index, | 
|  | m_sedge->m_dest->m_index); | 
|  | else | 
|  | return pp_printf (&pp, | 
|  | "taking edge SN:%i -> SN:%i", | 
|  | m_sedge->m_src->m_index, | 
|  | m_sedge->m_dest->m_index); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Attempt to generate a description of any condition that holds at this edge. | 
|  |  | 
|  | The intent is to make the user-facing messages more clear, especially for | 
|  | cases where there's a single or double-negative, such as | 
|  | when describing the false branch of an inverted condition. | 
|  |  | 
|  | For example, rather than printing just: | 
|  |  | 
|  | |  if (!ptr) | 
|  | |     ~ | 
|  | |     | | 
|  | |     (1) following 'false' branch... | 
|  |  | 
|  | it's clearer to spell out the condition that holds: | 
|  |  | 
|  | |  if (!ptr) | 
|  | |     ~ | 
|  | |     | | 
|  | |     (1) following 'false' branch (when 'ptr' is non-NULL)... | 
|  | ^^^^^^^^^^^^^^^^^^^^^^ | 
|  |  | 
|  | In the above example, this function would generate the highlighted | 
|  | string: "when 'ptr' is non-NULL". | 
|  |  | 
|  | If the edge is not a condition, or it's not clear that a description of | 
|  | the condition would be helpful to the user, return NULL.  */ | 
|  |  | 
|  | label_text | 
|  | start_cfg_edge_event::maybe_describe_condition (bool can_colorize) const | 
|  | { | 
|  | const cfg_superedge& cfg_sedge = get_cfg_superedge (); | 
|  |  | 
|  | if (cfg_sedge.true_value_p () || cfg_sedge.false_value_p ()) | 
|  | { | 
|  | const gimple *last_stmt = m_sedge->m_src->get_last_stmt (); | 
|  | if (const gcond *cond_stmt = dyn_cast <const gcond *> (last_stmt)) | 
|  | { | 
|  | enum tree_code op = gimple_cond_code (cond_stmt); | 
|  | tree lhs = gimple_cond_lhs (cond_stmt); | 
|  | tree rhs = gimple_cond_rhs (cond_stmt); | 
|  | if (cfg_sedge.false_value_p ()) | 
|  | op = invert_tree_comparison (op, false /* honor_nans */); | 
|  | return maybe_describe_condition (can_colorize, | 
|  | lhs, op, rhs); | 
|  | } | 
|  | } | 
|  | return label_text::borrow (nullptr); | 
|  | } | 
|  |  | 
|  | /* Subroutine of maybe_describe_condition above. | 
|  |  | 
|  | Attempt to generate a user-facing description of the condition | 
|  | LHS OP RHS, but only if it is likely to make it easier for the | 
|  | user to understand a condition.  */ | 
|  |  | 
|  | label_text | 
|  | start_cfg_edge_event::maybe_describe_condition (bool can_colorize, | 
|  | tree lhs, | 
|  | enum tree_code op, | 
|  | tree rhs) | 
|  | { | 
|  | /* In theory we could just build a tree via | 
|  | fold_build2 (op, boolean_type_node, lhs, rhs) | 
|  | and print it with %qE on it, but this leads to warts such as | 
|  | parenthesizing vars, such as '(i) <= 9', and uses of '<unknown>'.  */ | 
|  |  | 
|  | /* Special-case: describe testing the result of strcmp, as figuring | 
|  | out what the "true" or "false" path is can be confusing to the user.  */ | 
|  | if (TREE_CODE (lhs) == SSA_NAME | 
|  | && zerop (rhs)) | 
|  | { | 
|  | if (gcall *call = dyn_cast <gcall *> (SSA_NAME_DEF_STMT (lhs))) | 
|  | if (is_special_named_call_p (*call, "strcmp", 2)) | 
|  | { | 
|  | if (op == EQ_EXPR) | 
|  | return label_text::borrow ("when the strings are equal"); | 
|  | if (op == NE_EXPR) | 
|  | return label_text::borrow ("when the strings are non-equal"); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Only attempt to generate text for sufficiently simple expressions.  */ | 
|  | if (!should_print_expr_p (lhs)) | 
|  | return label_text::borrow (nullptr); | 
|  | if (!should_print_expr_p (rhs)) | 
|  | return label_text::borrow (nullptr); | 
|  |  | 
|  | /* Special cases for pointer comparisons against NULL.  */ | 
|  | if (POINTER_TYPE_P (TREE_TYPE (lhs)) | 
|  | && POINTER_TYPE_P (TREE_TYPE (rhs)) | 
|  | && zerop (rhs)) | 
|  | { | 
|  | if (op == EQ_EXPR) | 
|  | return make_label_text (can_colorize, "when %qE is NULL", | 
|  | lhs); | 
|  | if (op == NE_EXPR) | 
|  | return make_label_text (can_colorize, "when %qE is non-NULL", | 
|  | lhs); | 
|  | } | 
|  |  | 
|  | return make_label_text (can_colorize, "when %<%E %s %E%>", | 
|  | lhs, op_symbol_code (op), rhs); | 
|  | } | 
|  |  | 
|  | /* Subroutine of maybe_describe_condition. | 
|  |  | 
|  | Return true if EXPR is we will get suitable user-facing output | 
|  | from %E on it.  */ | 
|  |  | 
|  | bool | 
|  | start_cfg_edge_event::should_print_expr_p (tree expr) | 
|  | { | 
|  | if (TREE_CODE (expr) == SSA_NAME) | 
|  | { | 
|  | if (SSA_NAME_VAR (expr)) | 
|  | return should_print_expr_p (SSA_NAME_VAR (expr)); | 
|  | else | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (DECL_P (expr)) | 
|  | return true; | 
|  |  | 
|  | if (CONSTANT_CLASS_P (expr)) | 
|  | return true; | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | /* class call_event : public superedge_event.  */ | 
|  |  | 
|  | /* call_event's ctor.  */ | 
|  |  | 
|  | call_event::call_event (const exploded_edge &eedge, | 
|  | const event_loc_info &loc_info) | 
|  | : superedge_event (event_kind::call_edge, eedge, loc_info) | 
|  | { | 
|  | if (eedge.m_sedge) | 
|  | gcc_assert (eedge.m_sedge->m_kind == SUPEREDGE_CALL); | 
|  |  | 
|  | m_src_snode = eedge.m_src->get_supernode (); | 
|  | m_dest_snode = eedge.m_dest->get_supernode (); | 
|  | } | 
|  |  | 
|  | /* Implementation of diagnostics::paths::event::print_desc vfunc for | 
|  | call_event. | 
|  |  | 
|  | If this call event passes critical state for an sm-based warning, | 
|  | allow the diagnostic to generate a precise description, such as: | 
|  |  | 
|  | "passing freed pointer 'ptr' in call to 'foo' from 'bar'" | 
|  |  | 
|  | Otherwise, generate a description of the form | 
|  | "calling 'foo' from 'bar'".  */ | 
|  |  | 
|  | void | 
|  | call_event::print_desc (pretty_printer &pp) const | 
|  | { | 
|  | if (m_critical_state && m_pending_diagnostic) | 
|  | { | 
|  | gcc_assert (m_var); | 
|  | tree var = fixup_tree_for_diagnostic (m_var); | 
|  | evdesc::call_with_state evd (m_src_snode->m_fun->decl, | 
|  | m_dest_snode->m_fun->decl, | 
|  | var, | 
|  | m_critical_state); | 
|  | if (m_pending_diagnostic->describe_call_with_state (pp, evd)) | 
|  | return; | 
|  | } | 
|  |  | 
|  | pp_printf (&pp, | 
|  | "calling %qE from %qE", | 
|  | get_callee_fndecl (), | 
|  | get_caller_fndecl ()); | 
|  | } | 
|  |  | 
|  | /* Implementation of diagnostics::paths::event::get_meaning vfunc for | 
|  | function call events.  */ | 
|  |  | 
|  | diagnostics::paths::event::meaning | 
|  | call_event::get_meaning () const | 
|  | { | 
|  | return meaning (verb::call, noun::function); | 
|  | } | 
|  |  | 
|  | /* Override of checker_event::is_call_p for calls.  */ | 
|  |  | 
|  | bool | 
|  | call_event::is_call_p () const | 
|  | { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | tree | 
|  | call_event::get_caller_fndecl () const | 
|  | { | 
|  | return m_src_snode->m_fun->decl; | 
|  | } | 
|  |  | 
|  | tree | 
|  | call_event::get_callee_fndecl () const | 
|  | { | 
|  | return m_dest_snode->m_fun->decl; | 
|  | } | 
|  |  | 
|  | const program_state * | 
|  | call_event::get_program_state () const | 
|  | { | 
|  | /* Use the state at the source (at the caller), | 
|  | rather than the one at the dest, which has a frame for the callee.  */ | 
|  | return &m_eedge.m_src->get_state (); | 
|  | } | 
|  |  | 
|  | /* class return_event : public superedge_event.  */ | 
|  |  | 
|  | /* return_event's ctor.  */ | 
|  |  | 
|  | return_event::return_event (const exploded_edge &eedge, | 
|  | const event_loc_info &loc_info) | 
|  | : superedge_event (event_kind::return_edge, eedge, loc_info) | 
|  | { | 
|  | if (eedge.m_sedge) | 
|  | gcc_assert (eedge.m_sedge->m_kind == SUPEREDGE_RETURN); | 
|  |  | 
|  | m_src_snode = eedge.m_src->get_supernode (); | 
|  | m_dest_snode = eedge.m_dest->get_supernode (); | 
|  | } | 
|  |  | 
|  | /* Implementation of diagnostics::paths::event::print_desc vfunc for | 
|  | return_event. | 
|  |  | 
|  | If this return event returns critical state for an sm-based warning, | 
|  | allow the diagnostic to generate a precise description, such as: | 
|  |  | 
|  | "possible of NULL to 'foo' from 'bar'" | 
|  |  | 
|  | Otherwise, generate a description of the form | 
|  | "returning to 'foo' from 'bar'.  */ | 
|  |  | 
|  | void | 
|  | return_event::print_desc (pretty_printer &pp) const | 
|  | { | 
|  | /*  For greatest precision-of-wording, if this is returning the | 
|  | state involved in the pending diagnostic, give the pending | 
|  | diagnostic a chance to describe this return (in terms of | 
|  | itself).  */ | 
|  | if (m_critical_state && m_pending_diagnostic) | 
|  | { | 
|  | evdesc::return_of_state evd (m_dest_snode->m_fun->decl, | 
|  | m_src_snode->m_fun->decl, | 
|  | m_critical_state); | 
|  | if (m_pending_diagnostic->describe_return_of_state (pp, evd)) | 
|  | return; | 
|  | } | 
|  | pp_printf (&pp, | 
|  | "returning to %qE from %qE", | 
|  | m_dest_snode->m_fun->decl, | 
|  | m_src_snode->m_fun->decl); | 
|  | } | 
|  |  | 
|  | /* Implementation of diagnostics::paths::event::get_meaning vfunc for | 
|  | function return events.  */ | 
|  |  | 
|  | diagnostics::paths::event::meaning | 
|  | return_event::get_meaning () const | 
|  | { | 
|  | return meaning (verb::return_, noun::function); | 
|  | } | 
|  |  | 
|  | /* Override of checker_event::is_return_p for returns.  */ | 
|  |  | 
|  | bool | 
|  | return_event::is_return_p () const | 
|  | { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /* class start_consolidated_cfg_edges_event : public checker_event.  */ | 
|  |  | 
|  | void | 
|  | start_consolidated_cfg_edges_event::print_desc (pretty_printer &pp) const | 
|  | { | 
|  | pp_printf (&pp, | 
|  | "following %qs branch...", | 
|  | m_edge_sense ? "true" : "false"); | 
|  | } | 
|  |  | 
|  | /* Implementation of diagnostics::paths::event::get_meaning vfunc for | 
|  | start_consolidated_cfg_edges_event.  */ | 
|  |  | 
|  | diagnostics::paths::event::meaning | 
|  | start_consolidated_cfg_edges_event::get_meaning () const | 
|  | { | 
|  | return meaning (verb::branch, | 
|  | (m_edge_sense ? property::true_ : property::false_)); | 
|  | } | 
|  |  | 
|  | /* class inlined_call_event : public checker_event.  */ | 
|  |  | 
|  | void | 
|  | inlined_call_event::print_desc (pretty_printer &pp) const | 
|  | { | 
|  | pp_printf (&pp, | 
|  | "inlined call to %qE from %qE", | 
|  | m_apparent_callee_fndecl, | 
|  | m_apparent_caller_fndecl); | 
|  | } | 
|  |  | 
|  | /* Implementation of diagnostics::paths::event::get_meaning vfunc for | 
|  | reconstructed inlined function calls.  */ | 
|  |  | 
|  | diagnostics::paths::event::meaning | 
|  | inlined_call_event::get_meaning () const | 
|  | { | 
|  | return meaning (verb::call, noun::function); | 
|  | } | 
|  |  | 
|  | /* class setjmp_event : public checker_event.  */ | 
|  |  | 
|  | /* Implementation of diagnostics::paths::event::print_desc vfunc for | 
|  | setjmp_event.  */ | 
|  |  | 
|  | void | 
|  | setjmp_event::print_desc (pretty_printer &pp) const | 
|  | { | 
|  | pp_printf (&pp, | 
|  | "%qs called here", | 
|  | get_user_facing_name (m_setjmp_call)); | 
|  | } | 
|  |  | 
|  | /* Implementation of checker_event::prepare_for_emission vfunc for setjmp_event. | 
|  |  | 
|  | Record this setjmp's event ID into the path, so that rewind events can | 
|  | use it.  */ | 
|  |  | 
|  | void | 
|  | setjmp_event::prepare_for_emission (checker_path *path, | 
|  | pending_diagnostic *pd, | 
|  | diagnostics::paths::event_id_t emission_id) | 
|  | { | 
|  | checker_event::prepare_for_emission (path, pd, emission_id); | 
|  | path->record_setjmp_event (m_enode, emission_id); | 
|  | } | 
|  |  | 
|  | /* class rewind_event : public checker_event.  */ | 
|  |  | 
|  | /* Get the fndecl containing the site of the longjmp call.  */ | 
|  |  | 
|  | tree | 
|  | rewind_event::get_longjmp_caller () const | 
|  | { | 
|  | return m_eedge->m_src->get_function ()->decl; | 
|  | } | 
|  |  | 
|  | /* Get the fndecl containing the site of the setjmp call.  */ | 
|  |  | 
|  | tree | 
|  | rewind_event::get_setjmp_caller () const | 
|  | { | 
|  | return m_eedge->m_dest->get_function ()->decl; | 
|  | } | 
|  |  | 
|  | /* rewind_event's ctor.  */ | 
|  |  | 
|  | rewind_event::rewind_event (const exploded_edge *eedge, | 
|  | enum event_kind kind, | 
|  | const event_loc_info &loc_info, | 
|  | const rewind_info_t *rewind_info) | 
|  | : checker_event (kind, loc_info), | 
|  | m_rewind_info (rewind_info), | 
|  | m_eedge (eedge) | 
|  | { | 
|  | gcc_assert (m_eedge->m_custom_info.get () == m_rewind_info); | 
|  | } | 
|  |  | 
|  | /* class rewind_from_longjmp_event : public rewind_event.  */ | 
|  |  | 
|  | /* Implementation of diagnostics::paths::event::print_desc vfunc for | 
|  | rewind_from_longjmp_event.  */ | 
|  |  | 
|  | void | 
|  | rewind_from_longjmp_event::print_desc (pretty_printer &pp) const | 
|  | { | 
|  | const char *src_name | 
|  | = get_user_facing_name (m_rewind_info->get_longjmp_call ()); | 
|  |  | 
|  | if (get_longjmp_caller () == get_setjmp_caller ()) | 
|  | /* Special-case: purely intraprocedural rewind.  */ | 
|  | pp_printf (&pp, | 
|  | "rewinding within %qE from %qs...", | 
|  | get_longjmp_caller (), | 
|  | src_name); | 
|  | else | 
|  | pp_printf (&pp, | 
|  | "rewinding from %qs in %qE...", | 
|  | src_name, | 
|  | get_longjmp_caller ()); | 
|  | } | 
|  |  | 
|  | /* class rewind_to_setjmp_event : public rewind_event.  */ | 
|  |  | 
|  | /* Implementation of diagnostics::paths::event::print_desc vfunc for | 
|  | rewind_to_setjmp_event.  */ | 
|  |  | 
|  | void | 
|  | rewind_to_setjmp_event::print_desc (pretty_printer &pp) const | 
|  | { | 
|  | const char *dst_name | 
|  | = get_user_facing_name (m_rewind_info->get_setjmp_call ()); | 
|  |  | 
|  | /* If we can, identify the ID of the setjmp_event.  */ | 
|  | if (m_original_setjmp_event_id.known_p ()) | 
|  | { | 
|  | if (get_longjmp_caller () == get_setjmp_caller ()) | 
|  | /* Special-case: purely intraprocedural rewind.  */ | 
|  | pp_printf (&pp, | 
|  | "...to %qs (saved at %@)", | 
|  | dst_name, | 
|  | &m_original_setjmp_event_id); | 
|  | else | 
|  | pp_printf (&pp, | 
|  | "...to %qs in %qE (saved at %@)", | 
|  | dst_name, | 
|  | get_setjmp_caller (), | 
|  | &m_original_setjmp_event_id); | 
|  | } | 
|  | else | 
|  | { | 
|  | if (get_longjmp_caller () == get_setjmp_caller ()) | 
|  | /* Special-case: purely intraprocedural rewind.  */ | 
|  | pp_printf (&pp, | 
|  | "...to %qs", | 
|  | dst_name); | 
|  | else | 
|  | pp_printf (&pp, | 
|  | "...to %qs in %qE", | 
|  | dst_name, | 
|  | get_setjmp_caller ()); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Implementation of checker_event::prepare_for_emission vfunc for | 
|  | rewind_to_setjmp_event. | 
|  |  | 
|  | Attempt to look up the setjmp event ID that recorded the jmp_buf | 
|  | for this rewind.  */ | 
|  |  | 
|  | void | 
|  | rewind_to_setjmp_event::prepare_for_emission (checker_path *path, | 
|  | pending_diagnostic *pd, | 
|  | diagnostics::paths::event_id_t emission_id) | 
|  | { | 
|  | checker_event::prepare_for_emission (path, pd, emission_id); | 
|  | path->get_setjmp_event (m_rewind_info->get_enode_origin (), | 
|  | &m_original_setjmp_event_id); | 
|  | } | 
|  |  | 
|  | /* class throw_event : public checker_event.  */ | 
|  |  | 
|  | /* class explicit_throw_event : public throw_event.  */ | 
|  | void | 
|  | explicit_throw_event::print_desc (pretty_printer &pp) const | 
|  | { | 
|  | if (m_is_rethrow) | 
|  | { | 
|  | if (m_type) | 
|  | pp_printf (&pp, "rethrowing exception of type %qT here...", m_type); | 
|  | else | 
|  | pp_printf (&pp, "rethrowing exception here..."); | 
|  | } | 
|  | else | 
|  | { | 
|  | if (m_type) | 
|  | pp_printf (&pp, "throwing exception of type %qT here...", m_type); | 
|  | else | 
|  | pp_printf (&pp, "throwing exception here..."); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* class throw_from_call_to_external_fn_event : public throw_event.  */ | 
|  |  | 
|  | void | 
|  | throw_from_call_to_external_fn_event::print_desc (pretty_printer &pp) const | 
|  | { | 
|  | if (m_fndecl) | 
|  | pp_printf (&pp, "if %qD throws an exception...", m_fndecl); | 
|  | else | 
|  | pp_printf (&pp, "if the called function throws an exception..."); | 
|  | } | 
|  |  | 
|  | // class unwind_event : public checker_event | 
|  |  | 
|  | void | 
|  | unwind_event::print_desc (pretty_printer &pp) const | 
|  | { | 
|  | if (m_num_frames > 1) | 
|  | pp_printf (&pp, "unwinding %i stack frames", m_num_frames); | 
|  | else | 
|  | pp_printf (&pp, "unwinding stack frame"); | 
|  | } | 
|  |  | 
|  | /* class warning_event : public checker_event.  */ | 
|  |  | 
|  | /* Implementation of diagnostics::paths::event::print_desc vfunc for | 
|  | warning_event. | 
|  |  | 
|  | If the pending diagnostic implements describe_final_event, use it, | 
|  | generating a precise description e.g. | 
|  | "second 'free' here; first 'free' was at (7)" | 
|  |  | 
|  | Otherwise generate a generic description.  */ | 
|  |  | 
|  | void | 
|  | warning_event::print_desc (pretty_printer &pp) const | 
|  | { | 
|  | if (m_pending_diagnostic) | 
|  | { | 
|  | tree var = fixup_tree_for_diagnostic (m_var); | 
|  | evdesc::final_event evd (var, m_state, *this); | 
|  | if (m_pending_diagnostic->describe_final_event (pp, evd)) | 
|  | { | 
|  | if (m_sm && flag_analyzer_verbose_state_changes) | 
|  | { | 
|  | if (var) | 
|  | pp_printf (&pp, " (%qE is in state %qs)", | 
|  | var, m_state->get_name ()); | 
|  | else | 
|  | pp_printf (&pp, " (in global state %qs)", | 
|  | m_state->get_name ()); | 
|  | } | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (m_sm) | 
|  | { | 
|  | if (m_var) | 
|  | pp_printf (&pp, "here (%qE is in state %qs)", | 
|  | m_var, m_state->get_name ()); | 
|  | else | 
|  | pp_printf (&pp, "here (in global state %qs)", | 
|  | m_state->get_name ()); | 
|  | return; | 
|  | } | 
|  | else | 
|  | pp_string (&pp, "here"); | 
|  | } | 
|  |  | 
|  | /* Implementation of diagnostics::paths::event::get_meaning vfunc for | 
|  | warning_event.  */ | 
|  |  | 
|  | diagnostics::paths::event::meaning | 
|  | warning_event::get_meaning () const | 
|  | { | 
|  | return meaning (verb::danger, noun::unknown); | 
|  | } | 
|  |  | 
|  | const program_state * | 
|  | warning_event::get_program_state () const | 
|  | { | 
|  | if (m_program_state) | 
|  | return m_program_state.get (); | 
|  | else | 
|  | return &m_enode->get_state (); | 
|  | } | 
|  |  | 
|  | } // namespace ana | 
|  |  | 
|  | #endif /* #if ENABLE_ANALYZER */ |