| /* Subclasses of 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" |
| #define INCLUDE_MEMORY |
| #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/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 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 ()); |
| } |
| |
| /* Dump this event to stderr (for debugging/logging purposes). */ |
| |
| DEBUG_FUNCTION void |
| checker_event::debug () const |
| { |
| pretty_printer pp; |
| pp_format_decoder (&pp) = default_tree_printer; |
| pp_show_color (&pp) = pp_show_color (global_dc->printer); |
| pp.buffer->stream = stderr; |
| dump (&pp); |
| pp_newline (&pp); |
| pp_flush (&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 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. */ |
| |
| function_entry_event::function_entry_event (const program_point &dst_point) |
| : checker_event (EK_FUNCTION_ENTRY, |
| dst_point.get_supernode ()->get_start_location (), |
| dst_point.get_fndecl (), |
| dst_point.get_stack_depth ()) |
| { |
| } |
| |
| /* 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.get () == 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); |
| } |
| |
| } // namespace ana |
| |
| #endif /* #if ENABLE_ANALYZER */ |