| /* 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 */ |