| /* Subclasses of diagnostic_path and diagnostic_event for analyzer diagnostics. |
| Copyright (C) 2019-2022 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 "function.h" |
| #include "basic-block.h" |
| #include "gimple.h" |
| #include "diagnostic-core.h" |
| #include "gimple-pretty-print.h" |
| #include "fold-const.h" |
| #include "diagnostic-path.h" |
| #include "options.h" |
| #include "cgraph.h" |
| #include "cfg.h" |
| #include "digraph.h" |
| #include "diagnostic-event-id.h" |
| #include "analyzer/analyzer.h" |
| #include "analyzer/analyzer-logging.h" |
| #include "analyzer/sm.h" |
| #include "sbitmap.h" |
| #include "bitmap.h" |
| #include "ordered-hash-map.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 "gimple-iterator.h" |
| #include "inlining-iterator.h" |
| #include "analyzer/supergraph.h" |
| #include "analyzer/pending-diagnostic.h" |
| #include "analyzer/diagnostic-manager.h" |
| #include "analyzer/constraint-manager.h" |
| #include "analyzer/diagnostic-manager.h" |
| #include "analyzer/checker-path.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 EK_DEBUG: |
| return "EK_DEBUG"; |
| case EK_CUSTOM: |
| return "EK_CUSTOM"; |
| case EK_STMT: |
| return "EK_STMT"; |
| case EK_REGION_CREATION: |
| return "EK_REGION_CREATION"; |
| case EK_FUNCTION_ENTRY: |
| return "EK_FUNCTION_ENTRY"; |
| case EK_STATE_CHANGE: |
| return "EK_STATE_CHANGE"; |
| case EK_START_CFG_EDGE: |
| return "EK_START_CFG_EDGE"; |
| case EK_END_CFG_EDGE: |
| return "EK_END_CFG_EDGE"; |
| case EK_CALL_EDGE: |
| return "EK_CALL_EDGE"; |
| case EK_RETURN_EDGE: |
| return "EK_RETURN_EDGE"; |
| case EK_START_CONSOLIDATED_CFG_EDGES: |
| return "EK_START_CONSOLIDATED_CFG_EDGES"; |
| case EK_END_CONSOLIDATED_CFG_EDGES: |
| return "EK_END_CONSOLIDATED_CFG_EDGES"; |
| case EK_INLINED_CALL: |
| return "EK_INLINED_CALL"; |
| case EK_SETJMP: |
| return "EK_SETJMP"; |
| case EK_REWIND_FROM_LONGJMP: |
| return "EK_REWIND_FROM_LONGJMP"; |
| case EK_REWIND_TO_SETJMP: |
| return "EK_REWIND_TO_SETJMP"; |
| case EK_WARNING: |
| return "EK_WARNING"; |
| } |
| } |
| |
| /* A class for fixing up fndecls and stack depths in checker_event, based |
| on inlining records. |
| |
| The early inliner runs before the analyzer, which can lead to confusing |
| output. |
| |
| Tne base fndecl and depth within a checker_event are from call strings |
| in program_points, which reflect the call strings after inlining. |
| This class lets us offset the depth and fix up the reported fndecl and |
| stack depth to better reflect the user's original code. */ |
| |
| class inlining_info |
| { |
| public: |
| inlining_info (location_t loc) |
| { |
| inlining_iterator iter (loc); |
| m_inner_fndecl = iter.get_fndecl (); |
| int num_frames = 0; |
| while (!iter.done_p ()) |
| { |
| m_outer_fndecl = iter.get_fndecl (); |
| num_frames++; |
| iter.next (); |
| } |
| if (num_frames > 1) |
| m_extra_frames = num_frames - 1; |
| else |
| m_extra_frames = 0; |
| } |
| |
| tree get_inner_fndecl () const { return m_inner_fndecl; } |
| int get_extra_frames () const { return m_extra_frames; } |
| |
| private: |
| tree m_outer_fndecl; |
| tree m_inner_fndecl; |
| int m_extra_frames; |
| }; |
| |
| /* class checker_event : public diagnostic_event. */ |
| |
| /* checker_event's ctor. */ |
| |
| checker_event::checker_event (enum event_kind kind, |
| location_t loc, tree fndecl, int depth) |
| : m_kind (kind), m_loc (loc), |
| m_original_fndecl (fndecl), m_effective_fndecl (fndecl), |
| m_original_depth (depth), m_effective_depth (depth), |
| m_pending_diagnostic (NULL), m_emission_id (), |
| m_logical_loc (fndecl) |
| { |
| /* Update effective fndecl and depth if inlining has been recorded. */ |
| if (flag_analyzer_undo_inlining) |
| { |
| inlining_info info (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 (m_effective_fndecl); |
| } |
| } |
| } |
| |
| /* No-op implementation of diagnostic_event::get_meaning vfunc for |
| checker_event: checker events have no meaning by default. */ |
| |
| diagnostic_event::meaning |
| checker_event::get_meaning () const |
| { |
| return meaning (); |
| } |
| |
| /* Dump this event to PP (for debugging/logging purposes). */ |
| |
| void |
| checker_event::dump (pretty_printer *pp) const |
| { |
| label_text event_desc (get_desc (false)); |
| pp_printf (pp, "\"%s\" (depth %i", |
| event_desc.get (), 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=%x)", |
| get_location ()); |
| } |
| |
| /* 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 get_desc vfunc, so that any |
| side-effects of the call to get_desc take place before |
| pending_diagnostic::emit is called. |
| |
| For example, state_change_event::get_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 *, |
| pending_diagnostic *pd, |
| diagnostic_event_id_t emission_id) |
| { |
| m_pending_diagnostic = pd; |
| m_emission_id = emission_id; |
| |
| label_text desc = get_desc (false); |
| } |
| |
| /* class debug_event : public checker_event. */ |
| |
| /* Implementation of diagnostic_event::get_desc vfunc for |
| debug_event. |
| Use the saved string as the event's description. */ |
| |
| label_text |
| debug_event::get_desc (bool) const |
| { |
| return label_text::borrow (m_desc); |
| } |
| |
| /* class precanned_custom_event : public custom_event. */ |
| |
| /* Implementation of diagnostic_event::get_desc vfunc for |
| precanned_custom_event. |
| Use the saved string as the event's description. */ |
| |
| label_text |
| precanned_custom_event::get_desc (bool) const |
| { |
| return label_text::borrow (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 (EK_STMT, gimple_location (stmt), fndecl, depth), |
| m_stmt (stmt), |
| m_dst_state (dst_state) |
| { |
| } |
| |
| /* Implementation of diagnostic_event::get_desc vfunc for |
| statement_event. |
| Use the statement's dump form as the event's description. */ |
| |
| label_text |
| statement_event::get_desc (bool) const |
| { |
| pretty_printer pp; |
| pp_string (&pp, "stmt: "); |
| pp_gimple_stmt_1 (&pp, m_stmt, 0, (dump_flags_t)0); |
| return label_text::take (xstrdup (pp_formatted_text (&pp))); |
| } |
| |
| /* class region_creation_event : public checker_event. */ |
| |
| region_creation_event::region_creation_event (const region *reg, |
| tree capacity, |
| enum rce_kind kind, |
| location_t loc, |
| tree fndecl, |
| int depth) |
| : checker_event (EK_REGION_CREATION, loc, fndecl, depth), |
| m_reg (reg), |
| m_capacity (capacity), |
| m_rce_kind (kind) |
| { |
| if (m_rce_kind == RCE_CAPACITY) |
| gcc_assert (capacity); |
| } |
| |
| /* Implementation of diagnostic_event::get_desc vfunc for |
| region_creation_event. |
| There are effectively 3 kinds of region_region_event, to |
| avoid combinatorial explosion by trying to convy the |
| information in a single message. */ |
| |
| label_text |
| region_creation_event::get_desc (bool can_colorize) const |
| { |
| if (m_pending_diagnostic) |
| { |
| label_text custom_desc |
| = m_pending_diagnostic->describe_region_creation_event |
| (evdesc::region_creation (can_colorize, m_reg)); |
| if (custom_desc.get ()) |
| return custom_desc; |
| } |
| |
| switch (m_rce_kind) |
| { |
| default: |
| gcc_unreachable (); |
| |
| case RCE_MEM_SPACE: |
| switch (m_reg->get_memory_space ()) |
| { |
| default: |
| return label_text::borrow ("region created here"); |
| case MEMSPACE_STACK: |
| return label_text::borrow ("region created on stack here"); |
| case MEMSPACE_HEAP: |
| return label_text::borrow ("region created on heap here"); |
| } |
| break; |
| |
| case RCE_CAPACITY: |
| gcc_assert (m_capacity); |
| if (TREE_CODE (m_capacity) == INTEGER_CST) |
| { |
| unsigned HOST_WIDE_INT hwi = tree_to_uhwi (m_capacity); |
| if (hwi == 1) |
| return make_label_text (can_colorize, |
| "capacity: %wu byte", hwi); |
| else |
| return make_label_text (can_colorize, |
| "capacity: %wu bytes", hwi); |
| } |
| else |
| return make_label_text (can_colorize, |
| "capacity: %qE bytes", m_capacity); |
| |
| case RCE_DEBUG: |
| { |
| pretty_printer pp; |
| pp_format_decoder (&pp) = default_tree_printer; |
| pp_string (&pp, "region creation: "); |
| m_reg->dump_to_pp (&pp, true); |
| if (m_capacity) |
| pp_printf (&pp, " capacity: %qE", m_capacity); |
| return label_text::take (xstrdup (pp_formatted_text (&pp))); |
| } |
| break; |
| } |
| } |
| |
| /* class function_entry_event : public checker_event. */ |
| |
| /* Implementation of diagnostic_event::get_desc vfunc for |
| function_entry_event. |
| |
| Use a string such as "entry to 'foo'" as the event's description. */ |
| |
| label_text |
| function_entry_event::get_desc (bool can_colorize) const |
| { |
| return make_label_text (can_colorize, "entry to %qE", m_effective_fndecl); |
| } |
| |
| /* Implementation of diagnostic_event::get_meaning vfunc for |
| function entry. */ |
| |
| diagnostic_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) |
| : checker_event (EK_STATE_CHANGE, |
| 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) |
| { |
| } |
| |
| /* Implementation of diagnostic_event::get_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. */ |
| |
| label_text |
| state_change_event::get_desc (bool can_colorize) 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); |
| label_text custom_desc |
| = m_pending_diagnostic->describe_state_change |
| (evdesc::state_change (can_colorize, var, origin, |
| m_from, m_to, m_emission_id, *this)); |
| if (custom_desc.get ()) |
| { |
| if (flag_analyzer_verbose_state_changes) |
| { |
| /* Get any "meaning" of event. */ |
| diagnostic_event::meaning meaning = get_meaning (); |
| pretty_printer meaning_pp; |
| meaning.dump_to_pp (&meaning_pp); |
| |
| /* Append debug version. */ |
| if (m_origin) |
| return make_label_text |
| (can_colorize, |
| "%s (state of %qE: %qs -> %qs, origin: %qE, meaning: %s)", |
| custom_desc.get (), |
| var, |
| m_from->get_name (), |
| m_to->get_name (), |
| origin, |
| pp_formatted_text (&meaning_pp)); |
| else |
| return make_label_text |
| (can_colorize, |
| "%s (state of %qE: %qs -> %qs, NULL origin, meaning: %s)", |
| custom_desc.get (), |
| var, |
| m_from->get_name (), |
| m_to->get_name (), |
| pp_formatted_text (&meaning_pp)); |
| } |
| else |
| return custom_desc; |
| } |
| } |
| |
| /* Fallback description. */ |
| if (m_sval) |
| { |
| label_text sval_desc = m_sval->get_desc (); |
| if (m_origin) |
| { |
| label_text origin_desc = m_origin->get_desc (); |
| return make_label_text |
| (can_colorize, |
| "state of %qs: %qs -> %qs (origin: %qs)", |
| sval_desc.get (), |
| m_from->get_name (), |
| m_to->get_name (), |
| origin_desc.get ()); |
| } |
| else |
| return make_label_text |
| (can_colorize, |
| "state of %qs: %qs -> %qs (NULL origin)", |
| sval_desc.get (), |
| m_from->get_name (), |
| m_to->get_name ()); |
| } |
| else |
| { |
| gcc_assert (m_origin == NULL); |
| return make_label_text |
| (can_colorize, |
| "global state: %qs -> %qs", |
| m_from->get_name (), |
| m_to->get_name ()); |
| } |
| } |
| |
| /* Implementation of diagnostic_event::get_meaning vfunc for |
| state change events: delegate to the pending_diagnostic to |
| get any meaning. */ |
| |
| diagnostic_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); |
| return m_pending_diagnostic->get_meaning_for_state_change |
| (evdesc::state_change (false, var, origin, |
| m_from, m_to, m_emission_id, *this)); |
| } |
| else |
| return meaning (); |
| } |
| |
| /* class superedge_event : public checker_event. */ |
| |
| /* 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. */ |
| label_text desc = get_desc (false); |
| gcc_assert (desc.get ()); |
| if (desc.get ()[0] == '\0') |
| return true; |
| } |
| } |
| break; |
| |
| default: |
| break; |
| } |
| return false; |
| } |
| |
| /* superedge_event's ctor. */ |
| |
| superedge_event::superedge_event (enum event_kind kind, |
| const exploded_edge &eedge, |
| location_t loc, tree fndecl, int depth) |
| : checker_event (kind, loc, fndecl, depth), |
| m_eedge (eedge), m_sedge (eedge.m_sedge), |
| m_var (NULL_TREE), m_critical_state (0) |
| { |
| } |
| |
| /* 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, |
| location_t loc, tree fndecl, int depth) |
| : superedge_event (kind, eedge, loc, fndecl, depth) |
| { |
| gcc_assert (eedge.m_sedge->m_kind == SUPEREDGE_CFG_EDGE); |
| } |
| |
| /* Implementation of diagnostic_event::get_meaning vfunc for |
| CFG edge events. */ |
| |
| diagnostic_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 diagnostic_event::get_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, return an empty description (which will lead to this event |
| being filtered). */ |
| |
| label_text |
| start_cfg_edge_event::get_desc (bool can_colorize) 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 (can_colorize); |
| label_text result; |
| if (cond_desc.get ()) |
| return make_label_text (can_colorize, |
| "following %qs branch (%s)...", |
| edge_desc.get (), cond_desc.get ()); |
| else |
| return make_label_text (can_colorize, |
| "following %qs branch...", |
| edge_desc.get ()); |
| } |
| else |
| return label_text::borrow (""); |
| } |
| else |
| { |
| if (strlen (edge_desc.get ()) > 0) |
| return make_label_text (can_colorize, |
| "taking %qs edge SN:%i -> SN:%i", |
| edge_desc.get (), |
| m_sedge->m_src->m_index, |
| m_sedge->m_dest->m_index); |
| else |
| return make_label_text (can_colorize, |
| "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 (NULL); |
| } |
| |
| /* 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 (NULL); |
| if (!should_print_expr_p (rhs)) |
| return label_text::borrow (NULL); |
| |
| /* 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, |
| location_t loc, tree fndecl, int depth) |
| : superedge_event (EK_CALL_EDGE, eedge, loc, fndecl, depth) |
| { |
| 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 diagnostic_event::get_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'". */ |
| |
| label_text |
| call_event::get_desc (bool can_colorize) const |
| { |
| if (m_critical_state && m_pending_diagnostic) |
| { |
| gcc_assert (m_var); |
| tree var = fixup_tree_for_diagnostic (m_var); |
| label_text custom_desc |
| = m_pending_diagnostic->describe_call_with_state |
| (evdesc::call_with_state (can_colorize, |
| m_src_snode->m_fun->decl, |
| m_dest_snode->m_fun->decl, |
| var, |
| m_critical_state)); |
| if (custom_desc.get ()) |
| return custom_desc; |
| } |
| |
| return make_label_text (can_colorize, |
| "calling %qE from %qE", |
| get_callee_fndecl (), |
| get_caller_fndecl ()); |
| } |
| |
| /* Implementation of diagnostic_event::get_meaning vfunc for |
| function call events. */ |
| |
| diagnostic_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; |
| } |
| |
| /* class return_event : public superedge_event. */ |
| |
| /* return_event's ctor. */ |
| |
| return_event::return_event (const exploded_edge &eedge, |
| location_t loc, tree fndecl, int depth) |
| : superedge_event (EK_RETURN_EDGE, eedge, loc, fndecl, depth) |
| { |
| 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 diagnostic_event::get_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'. */ |
| |
| label_text |
| return_event::get_desc (bool can_colorize) 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) |
| { |
| label_text custom_desc |
| = m_pending_diagnostic->describe_return_of_state |
| (evdesc::return_of_state (can_colorize, |
| m_dest_snode->m_fun->decl, |
| m_src_snode->m_fun->decl, |
| m_critical_state)); |
| if (custom_desc.get ()) |
| return custom_desc; |
| } |
| return make_label_text (can_colorize, |
| "returning to %qE from %qE", |
| m_dest_snode->m_fun->decl, |
| m_src_snode->m_fun->decl); |
| } |
| |
| /* Implementation of diagnostic_event::get_meaning vfunc for |
| function return events. */ |
| |
| diagnostic_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. */ |
| |
| label_text |
| start_consolidated_cfg_edges_event::get_desc (bool can_colorize) const |
| { |
| return make_label_text (can_colorize, |
| "following %qs branch...", |
| m_edge_sense ? "true" : "false"); |
| } |
| |
| /* Implementation of diagnostic_event::get_meaning vfunc for |
| start_consolidated_cfg_edges_event. */ |
| |
| diagnostic_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. */ |
| |
| label_text |
| inlined_call_event::get_desc (bool can_colorize) const |
| { |
| return make_label_text (can_colorize, |
| "inlined call to %qE from %qE", |
| m_apparent_callee_fndecl, |
| m_apparent_caller_fndecl); |
| } |
| |
| /* Implementation of diagnostic_event::get_meaning vfunc for |
| reconstructed inlined function calls. */ |
| |
| diagnostic_event::meaning |
| inlined_call_event::get_meaning () const |
| { |
| return meaning (VERB_call, NOUN_function); |
| } |
| |
| /* class setjmp_event : public checker_event. */ |
| |
| /* Implementation of diagnostic_event::get_desc vfunc for |
| setjmp_event. */ |
| |
| label_text |
| setjmp_event::get_desc (bool can_colorize) const |
| { |
| return make_label_text (can_colorize, |
| "%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, |
| diagnostic_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, |
| location_t loc, tree fndecl, int depth, |
| const rewind_info_t *rewind_info) |
| : checker_event (kind, loc, fndecl, depth), |
| m_rewind_info (rewind_info), |
| m_eedge (eedge) |
| { |
| gcc_assert (m_eedge->m_custom_info == m_rewind_info); |
| } |
| |
| /* class rewind_from_longjmp_event : public rewind_event. */ |
| |
| /* Implementation of diagnostic_event::get_desc vfunc for |
| rewind_from_longjmp_event. */ |
| |
| label_text |
| rewind_from_longjmp_event::get_desc (bool can_colorize) 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. */ |
| return make_label_text (can_colorize, |
| "rewinding within %qE from %qs...", |
| get_longjmp_caller (), |
| src_name); |
| else |
| return make_label_text (can_colorize, |
| "rewinding from %qs in %qE...", |
| src_name, |
| get_longjmp_caller ()); |
| } |
| |
| /* class rewind_to_setjmp_event : public rewind_event. */ |
| |
| /* Implementation of diagnostic_event::get_desc vfunc for |
| rewind_to_setjmp_event. */ |
| |
| label_text |
| rewind_to_setjmp_event::get_desc (bool can_colorize) 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. */ |
| return make_label_text (can_colorize, |
| "...to %qs (saved at %@)", |
| dst_name, |
| &m_original_setjmp_event_id); |
| else |
| return make_label_text (can_colorize, |
| "...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. */ |
| return make_label_text (can_colorize, |
| "...to %qs", |
| dst_name, |
| get_setjmp_caller ()); |
| else |
| return make_label_text (can_colorize, |
| "...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, |
| diagnostic_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 warning_event : public checker_event. */ |
| |
| /* Implementation of diagnostic_event::get_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. */ |
| |
| label_text |
| warning_event::get_desc (bool can_colorize) const |
| { |
| if (m_pending_diagnostic) |
| { |
| tree var = fixup_tree_for_diagnostic (m_var); |
| label_text ev_desc |
| = m_pending_diagnostic->describe_final_event |
| (evdesc::final_event (can_colorize, var, m_state)); |
| if (ev_desc.get ()) |
| { |
| if (m_sm && flag_analyzer_verbose_state_changes) |
| { |
| if (var) |
| return make_label_text (can_colorize, |
| "%s (%qE is in state %qs)", |
| ev_desc.get (), |
| var, m_state->get_name ()); |
| else |
| return make_label_text (can_colorize, |
| "%s (in global state %qs)", |
| ev_desc.get (), |
| m_state->get_name ()); |
| } |
| else |
| return ev_desc; |
| } |
| } |
| |
| if (m_sm) |
| { |
| if (m_var) |
| return make_label_text (can_colorize, |
| "here (%qE is in state %qs)", |
| m_var, m_state->get_name ()); |
| else |
| return make_label_text (can_colorize, |
| "here (in global state %qs)", |
| m_state->get_name ()); |
| } |
| else |
| return label_text::borrow ("here"); |
| } |
| |
| /* Implementation of diagnostic_event::get_meaning vfunc for |
| warning_event. */ |
| |
| diagnostic_event::meaning |
| warning_event::get_meaning () const |
| { |
| return meaning (VERB_danger, NOUN_unknown); |
| } |
| |
| /* Print a single-line representation of this path to PP. */ |
| |
| void |
| checker_path::dump (pretty_printer *pp) const |
| { |
| pp_character (pp, '['); |
| |
| checker_event *e; |
| int i; |
| FOR_EACH_VEC_ELT (m_events, i, e) |
| { |
| if (i > 0) |
| pp_string (pp, ", "); |
| label_text event_desc (e->get_desc (false)); |
| pp_printf (pp, "\"%s\"", event_desc.get ()); |
| } |
| pp_character (pp, ']'); |
| } |
| |
| /* Print a multiline form of this path to LOGGER, prefixing it with DESC. */ |
| |
| void |
| checker_path::maybe_log (logger *logger, const char *desc) const |
| { |
| if (!logger) |
| return; |
| logger->start_log_line (); |
| logger->log_partial ("%s: ", desc); |
| dump (logger->get_printer ()); |
| logger->end_log_line (); |
| for (unsigned i = 0; i < m_events.length (); i++) |
| { |
| logger->start_log_line (); |
| logger->log_partial ("%s[%i]: %s ", desc, i, |
| event_kind_to_string (m_events[i]->m_kind)); |
| m_events[i]->dump (logger->get_printer ()); |
| logger->end_log_line (); |
| } |
| } |
| |
| /* Print a multiline form of this path to STDERR. */ |
| |
| DEBUG_FUNCTION void |
| checker_path::debug () const |
| { |
| checker_event *e; |
| int i; |
| FOR_EACH_VEC_ELT (m_events, i, e) |
| { |
| label_text event_desc (e->get_desc (false)); |
| fprintf (stderr, |
| "[%i]: %s \"%s\"\n", |
| i, |
| event_kind_to_string (m_events[i]->m_kind), |
| event_desc.get ()); |
| } |
| } |
| |
| /* Add region_creation_event instances to this path for REG, |
| describing whether REG is on the stack or heap and what |
| its capacity is (if known). |
| If DEBUG is true, also create an RCE_DEBUG event. */ |
| |
| void |
| checker_path::add_region_creation_events (const region *reg, |
| const region_model *model, |
| location_t loc, |
| tree fndecl, int depth, |
| bool debug) |
| { |
| tree capacity = NULL_TREE; |
| if (model) |
| if (const svalue *capacity_sval = model->get_capacity (reg)) |
| capacity = model->get_representative_tree (capacity_sval); |
| |
| add_event (new region_creation_event (reg, capacity, RCE_MEM_SPACE, |
| loc, fndecl, depth)); |
| |
| if (capacity) |
| add_event (new region_creation_event (reg, capacity, RCE_CAPACITY, |
| loc, fndecl, depth)); |
| |
| if (debug) |
| add_event (new region_creation_event (reg, capacity, RCE_DEBUG, |
| loc, fndecl, depth)); |
| } |
| |
| /* Add a warning_event to the end of this path. */ |
| |
| void |
| checker_path::add_final_event (const state_machine *sm, |
| const exploded_node *enode, const gimple *stmt, |
| tree var, state_machine::state_t state) |
| { |
| checker_event *end_of_path |
| = new warning_event (get_stmt_location (stmt, enode->get_function ()), |
| enode->get_function ()->decl, |
| enode->get_stack_depth (), |
| sm, var, state); |
| add_event (end_of_path); |
| } |
| |
| void |
| checker_path::fixup_locations (pending_diagnostic *pd) |
| { |
| for (checker_event *e : m_events) |
| e->set_location (pd->fixup_location (e->get_location ())); |
| } |
| |
| /* Return true if there is a (start_cfg_edge_event, end_cfg_edge_event) pair |
| at (IDX, IDX + 1). */ |
| |
| bool |
| checker_path::cfg_edge_pair_at_p (unsigned idx) const |
| { |
| if (m_events.length () < idx + 1) |
| return false; |
| return (m_events[idx]->m_kind == EK_START_CFG_EDGE |
| && m_events[idx + 1]->m_kind == EK_END_CFG_EDGE); |
| } |
| |
| /* Consider a call from "outer" to "middle" which calls "inner", |
| where "inner" and "middle" have been inlined into "outer". |
| |
| We expect the stmt locations for the inlined stmts to have a |
| chain like: |
| |
| [{fndecl: inner}, |
| {fndecl: middle, callsite: within middle to inner}, |
| {fndecl: outer, callsite: without outer to middle}] |
| |
| The location for the stmt will already be fixed up to reflect |
| the two extra frames, so that we have e.g. this as input |
| (for gcc.dg/analyzer/inlining-4.c): |
| |
| before[0]: |
| EK_FUNCTION_ENTRY "entry to ‘outer’" |
| (depth 1, fndecl ‘outer’, m_loc=511c4) |
| before[1]: |
| EK_START_CFG_EDGE "following ‘true’ branch (when ‘flag != 0’)..." |
| (depth 3 corrected from 1, |
| fndecl ‘inner’ corrected from ‘outer’, m_loc=8000000f) |
| before[2]: |
| EK_END_CFG_EDGE "...to here" |
| (depth 1, fndecl ‘outer’, m_loc=0) |
| before[3]: |
| EK_WARNING "here (‘<unknown>’ is in state ‘null’)" |
| (depth 1, fndecl ‘outer’, m_loc=80000004) |
| |
| We want to add inlined_call_events showing the calls, so that |
| the above becomes: |
| |
| after[0]: |
| EK_FUNCTION_ENTRY "entry to ‘outer’" |
| (depth 1, fndecl ‘outer’, m_loc=511c4) |
| after[1]: |
| EK_INLINED_CALL "inlined call to ‘middle’ from ‘outer’" |
| (depth 1, fndecl ‘outer’, m_loc=53300) |
| after[2]: |
| EK_INLINED_CALL "inlined call to ‘inner’ from ‘middle’" |
| (depth 2, fndecl ‘middle’, m_loc=4d2e0) |
| after[3]: |
| EK_START_CFG_EDGE "following ‘true’ branch (when ‘flag != 0’)..." |
| (depth 3 corrected from 1, |
| fndecl ‘inner’ corrected from ‘outer’, m_loc=8000000f) |
| after[4]: EK_END_CFG_EDGE "...to here" |
| (depth 1, fndecl ‘outer’, m_loc=0) |
| after[5]: EK_WARNING "here (‘<unknown>’ is in state ‘null’)" |
| (depth 1, fndecl ‘outer’, m_loc=80000004) |
| |
| where we've added events between before[0] and before[1] to show |
| the inlined calls leading to the effective stack depths, making |
| the generated path much easier for a user to read. |
| |
| Note how in the above we have a branch (before[1] to before[2]) |
| where the locations were originally in different functions. |
| Hence we have to add these events quite late when generating |
| checker_path. */ |
| |
| void |
| checker_path::inject_any_inlined_call_events (logger *logger) |
| { |
| LOG_SCOPE (logger); |
| |
| if (!flag_analyzer_undo_inlining) |
| return; |
| |
| /* Build a copy of m_events with the new events inserted. */ |
| auto_vec<checker_event *> updated_events; |
| |
| maybe_log (logger, "before"); |
| |
| hash_set<tree> blocks_in_prev_event; |
| |
| for (unsigned ev_idx = 0; ev_idx < m_events.length (); ev_idx++) |
| { |
| checker_event *curr_event = m_events[ev_idx]; |
| location_t curr_loc = curr_event->get_location (); |
| hash_set<tree> blocks_in_curr_event; |
| |
| if (logger) |
| { |
| logger->start_log_line (); |
| logger->log_partial ("event[%i]: %s ", ev_idx, |
| event_kind_to_string (curr_event->m_kind)); |
| curr_event->dump (logger->get_printer ()); |
| logger->end_log_line (); |
| for (inlining_iterator iter (curr_event->get_location ()); |
| !iter.done_p (); iter.next ()) |
| { |
| logger->start_log_line (); |
| logger->log_partial (" %qE (%p), fndecl: %qE, callsite: 0x%x", |
| iter.get_block (), iter.get_block (), |
| iter.get_fndecl (), iter.get_callsite ()); |
| if (iter.get_callsite ()) |
| dump_location (logger->get_printer (), iter.get_callsite ()); |
| logger->end_log_line (); |
| } |
| } |
| |
| /* We want to add events to show inlined calls. |
| |
| We want to show changes relative to the previous event, omitting |
| the commonality between the inlining chain. |
| |
| The chain is ordered from innermost frame to outermost frame; |
| we want to walk it backwards to show the calls, so capture it |
| in a vec. */ |
| struct chain_element { tree m_block; tree m_fndecl; }; |
| auto_vec<chain_element> elements; |
| for (inlining_iterator iter (curr_loc); !iter.done_p (); iter.next ()) |
| { |
| chain_element ce; |
| ce.m_block = iter.get_block (); |
| ce.m_fndecl = iter.get_fndecl (); |
| |
| if (!blocks_in_prev_event.contains (ce.m_block)) |
| elements.safe_push (ce); |
| blocks_in_curr_event.add (ce.m_block); |
| } |
| |
| /* Walk from outermost to innermost. */ |
| if (elements.length () > 0) |
| { |
| int orig_stack_depth = curr_event->get_original_stack_depth (); |
| for (unsigned element_idx = elements.length () - 1; element_idx > 0; |
| element_idx--) |
| { |
| const chain_element &ce = elements[element_idx]; |
| int stack_depth_adjustment |
| = (blocks_in_curr_event.elements () - element_idx) - 1; |
| if (location_t callsite = BLOCK_SOURCE_LOCATION (ce.m_block)) |
| updated_events.safe_push |
| (new inlined_call_event (callsite, |
| elements[element_idx - 1].m_fndecl, |
| ce.m_fndecl, |
| orig_stack_depth, |
| stack_depth_adjustment)); |
| } |
| } |
| |
| /* Ideally we'd use assignment here: |
| blocks_in_prev_event = blocks_in_curr_event; */ |
| blocks_in_prev_event.empty (); |
| for (auto iter : blocks_in_curr_event) |
| blocks_in_prev_event.add (iter); |
| |
| /* Add the existing event. */ |
| updated_events.safe_push (curr_event); |
| } |
| |
| /* Replace m_events with updated_events. */ |
| m_events.truncate (0); |
| m_events.safe_splice (updated_events); |
| |
| maybe_log (logger, " after"); |
| } |
| |
| } // namespace ana |
| |
| #endif /* #if ENABLE_ANALYZER */ |