aboutsummaryrefslogtreecommitdiff
path: root/gcc/analyzer/infinite-loop.cc
diff options
context:
space:
mode:
authorDavid Malcolm <dmalcolm@redhat.com>2023-11-17 19:55:25 -0500
committerDavid Malcolm <dmalcolm@redhat.com>2023-11-17 19:55:25 -0500
commit841008d3966c0fe7a80ec10703a50fbdab7620ac (patch)
treeae770077c20d4f2f2b70cbcf9d862d1a1480266e /gcc/analyzer/infinite-loop.cc
parentc63a0bbce57e89839317f10cefafccce9d4996a0 (diff)
downloadgcc-841008d3966c0fe7a80ec10703a50fbdab7620ac.zip
gcc-841008d3966c0fe7a80ec10703a50fbdab7620ac.tar.gz
gcc-841008d3966c0fe7a80ec10703a50fbdab7620ac.tar.bz2
analyzer: new warning: -Wanalyzer-infinite-loop [PR106147]
This patch implements a new analyzer warning: -Wanalyzer-infinite-loop. It works by examining the exploded graph once the latter has been fully built. It attempts to detect cycles in the exploded graph in which: - no externally visible work occurs - no escape is possible from the cycle once it has been entered - the program state is "sufficiently concrete" at each step: - no unknown activity could be occurring - the worklist was fully drained for each enode in the cycle i.e. every enode in the cycle is processed For example, it correctly complains about this bogus "for" loop: int sum = 0; for (struct node *iter = n; iter; iter->next) sum += n->val; return sum; like this: infinite-loop-linked-list.c: In function ‘for_loop_noop_next’: infinite-loop-linked-list.c:110:31: warning: infinite loop [CWE-835] [-Wanalyzer-infinite-loop] 110 | for (struct node *iter = n; iter; iter->next) | ^~~~ ‘for_loop_noop_next’: events 1-5 | | 110 | for (struct node *iter = n; iter; iter->next) | | ^~~~ | | | | | (1) infinite loop here | | (2) when ‘iter’ is non-NULL: always following ‘true’ branch... | | (5) ...to here | 111 | sum += n->val; | | ~~~~~~~~~~~~~ | | | | | | | (3) ...to here | | (4) looping back... | gcc/ChangeLog: PR analyzer/106147 * Makefile.in (ANALYZER_OBJS): Add analyzer/infinite-loop.o. * doc/invoke.texi: Add -fdump-analyzer-infinite-loop and -Wanalyzer-infinite-loop. Add missing CWE link for -Wanalyzer-infinite-recursion. * timevar.def (TV_ANALYZER_INFINITE_LOOPS): New. gcc/analyzer/ChangeLog: PR analyzer/106147 * analyzer.opt (Wanalyzer-infinite-loop): New option. (fdump-analyzer-infinite-loop): New option. * checker-event.h (start_cfg_edge_event::get_desc): Drop "final". (start_cfg_edge_event::maybe_describe_condition): Convert from private to protected. * checker-path.h (checker_path::get_logger): New. * diagnostic-manager.cc (process_worklist_item): Update for new context param of maybe_update_for_edge. * engine.cc (impl_region_model_context::impl_region_model_context): Add out_could_have_done_work param to both ctors and use it to initialize mm_out_could_have_done_work. (impl_region_model_context::maybe_did_work): New vfunc implementation. (exploded_node::on_stmt): Add out_could_have_done_work param and pass to ctxt ctor. (exploded_node::on_stmt_pre): Treat setjmp and longjmp as "doing work". (exploded_node::on_longjmp): Likewise. (exploded_edge::exploded_edge): Add "could_do_work" param and use it to initialize m_could_do_work_p. (exploded_edge::dump_dot_label): Add result of could_do_work_p. (exploded_graph::add_function_entry): Mark edge as doing no work. (exploded_graph::add_edge): Add "could_do_work" param and pass to exploded_edge ctor. (add_tainted_args_callback): Treat as doing no work. (exploded_graph::process_worklist): Likewise when merging nodes. (maybe_process_run_of_before_supernode_enodes::item): Likewise. (exploded_graph::maybe_create_dynamic_call): Likewise. (exploded_graph::process_node): Likewise for phi nodes. Pass in a "could_have_done_work" bool when handling stmts and use when creating edges. Assume work is done at bifurcation. (exploded_path::feasible_p): Update for new context param of maybe_update_for_edge. (feasibility_state::feasibility_state): New ctor. (feasibility_state::operator=): New. (feasibility_state::maybe_update_for_edge): Add ctxt param and use it. Fix missing newline when logging state. (impl_run_checkers): Call exploded_graph::detect_infinite_loops. * exploded-graph.h (impl_region_model_context::impl_region_model_context): Add out_could_have_done_work param to both ctors. (impl_region_model_context::maybe_did_work): New decl. (impl_region_model_context::checking_for_infinite_loop_p): New. (impl_region_model_context::on_unusable_in_infinite_loop): New. (impl_region_model_context::m_out_could_have_done_work): New field. (exploded_node::on_stmt): Add "out_could_have_done_work" param. (exploded_edge::exploded_edge): Add "could_do_work" param. (exploded_edge::could_do_work_p): New accessor. (exploded_edge::m_could_do_work_p): New field. (exploded_graph::add_edge): Add "could_do_work" param. (exploded_graph::detect_infinite_loops): New decl. (feasibility_state::feasibility_state): New ctor. (feasibility_state::operator=): New decl. (feasibility_state::maybe_update_for_edge): Add ctxt param. * infinite-loop.cc: New file. * program-state.cc (program_state::on_edge): Log the rejected constraint when region_model::maybe_update_for_edge fails. * region-model.cc (region_model::on_assignment): Treat any writes other than to the stack as "doing work". (region_model::on_stmt_pre): Treat all asm stmts as "doing work". (region_model::on_call_post): Likewise for all calls to functions with unknown side effects. (region_model::handle_phi): Add svals_changing_meaning param. Mark widening svalue in phi nodes as changing meaning. (unusable_in_infinite_loop_constraint_p): New. (region_model::add_constraint): If we're checking for an infinite loop, bail out on unusable svalues, or if we don't have a definite true/false for the constraint. (region_model::update_for_phis): Gather all svalues changing meaning in phi nodes, and purge constraints involving them. (region_model::replay_call_summary): Treat all call summaries as doing work. (region_model::can_merge_with_p): Purge constraints involving svalues that change meaning. (model_merger::on_widening_reuse): New. (test_iteration_1): Likewise. (selftest::test_iteration_1): Remove assertion that model6 "knows" that i < 157. * region-model.h (region_model::handle_phi): Add svals_changing_meaning param (region_model_context::maybe_did_work): New pure virtual func. (region_model_context::checking_for_infinite_loop_p): Likewise. (region_model_context::on_unusable_in_infinite_loop): Likewise. (noop_region_model_context::maybe_did_work): Implement. (noop_region_model_context::checking_for_infinite_loop_p): Likewise. (noop_region_model_context::on_unusable_in_infinite_loop): Likewise. (region_model_context_decorator::maybe_did_work): Implement. (region_model_context_decorator::checking_for_infinite_loop_p): Likewise. (region_model_context_decorator::on_unusable_in_infinite_loop): Likewise. (model_merger::on_widening_reuse): New decl. (model_merger::m_svals_changing_meaning): New field. * sm-signal.cc (register_signal_handler::impl_transition): Assume the edge "does work". * supergraph.cc (supernode::get_start_location): Use CFG edge's goto_locus if available. (supernode::get_end_location): Likewise. (cfg_superedge::dump_label_to_pp): Dump edges with a "goto_locus" * supergraph.h (cfg_superedge::get_goto_locus): New. * svalue.cc (svalue::can_merge_p): Call on_widening_reuse for widening values. (involvement_visitor::visit_widening_svalue): New. (svalue::involves_p): Update assertion to allow widening svalues. gcc/testsuite/ChangeLog: PR analyzer/106147 * c-c++-common/analyzer/gzio-2.c: Add dg-warning for infinite loop, marked as xfail. * c-c++-common/analyzer/infinite-loop-2.c: New test. * c-c++-common/analyzer/infinite-loop-4.c: New test. * c-c++-common/analyzer/infinite-loop-crc32c.c: New test. * c-c++-common/analyzer/infinite-loop-doom-d_main-IdentifyVersion.c: New test. * c-c++-common/analyzer/infinite-loop-doom-v_video.c: New test. * c-c++-common/analyzer/infinite-loop-g_error.c: New test. * c-c++-common/analyzer/infinite-loop-linked-list.c: New test. * c-c++-common/analyzer/infinite-recursion-inlining.c: Add dg-warning directives for infinite loop. * c-c++-common/analyzer/inlining-4-multiline.c: Update expected paths for event 5 having a location. * gcc.dg/analyzer/boxed-malloc-1.c: Add dg-warning for infinite loop. * gcc.dg/analyzer/data-model-20.c: Likewise. Add comment about suspect code, and create... * gcc.dg/analyzer/data-model-20a.c: ...this new test by cleaning it up. * gcc.dg/analyzer/edges-1.c: Add a placeholder statement to avoid the "...to here" from the if stmt occurring at the "while", and thus being treated as a bogus event. * gcc.dg/analyzer/explode-2a.c: Add dg-warning for infinite loop. * gcc.dg/analyzer/infinite-loop-1.c: New test. * gcc.dg/analyzer/malloc-1.c: Add dg-warning for infinite loop. * gcc.dg/analyzer/out-of-bounds-coreutils.c: Add TODO. * gcc.dg/analyzer/paths-4.c: Add dg-warning for infinite loop. * gcc.dg/analyzer/pr103892.c: Likewise. * gcc.dg/analyzer/pr93546.c: Likewise. Signed-off-by: David Malcolm <dmalcolm@redhat.com>
Diffstat (limited to 'gcc/analyzer/infinite-loop.cc')
-rw-r--r--gcc/analyzer/infinite-loop.cc565
1 files changed, 565 insertions, 0 deletions
diff --git a/gcc/analyzer/infinite-loop.cc b/gcc/analyzer/infinite-loop.cc
new file mode 100644
index 0000000..771d698
--- /dev/null
+++ b/gcc/analyzer/infinite-loop.cc
@@ -0,0 +1,565 @@
+/* Detection of infinite loops.
+ Copyright (C) 2022-2023 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
+#define INCLUDE_VECTOR
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "fold-const.h"
+#include "gcc-rich-location.h"
+#include "alloc-pool.h"
+#include "fibonacci_heap.h"
+#include "shortest-paths.h"
+#include "diagnostic-core.h"
+#include "diagnostic-event-id.h"
+#include "diagnostic-path.h"
+#include "diagnostic-metadata.h"
+#include "function.h"
+#include "pretty-print.h"
+#include "sbitmap.h"
+#include "bitmap.h"
+#include "tristate.h"
+#include "ordered-hash-map.h"
+#include "selftest.h"
+#include "json.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/analyzer-logging.h"
+#include "analyzer/call-string.h"
+#include "analyzer/program-point.h"
+#include "analyzer/store.h"
+#include "analyzer/region-model.h"
+#include "analyzer/constraint-manager.h"
+#include "analyzer/sm.h"
+#include "analyzer/pending-diagnostic.h"
+#include "analyzer/diagnostic-manager.h"
+#include "cfg.h"
+#include "basic-block.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "gimple-pretty-print.h"
+#include "cgraph.h"
+#include "digraph.h"
+#include "analyzer/supergraph.h"
+#include "analyzer/program-state.h"
+#include "analyzer/exploded-graph.h"
+#include "analyzer/checker-path.h"
+#include "analyzer/feasible-graph.h"
+#include "make-unique.h"
+
+/* A bundle of data characterizing a particular infinite loop
+ identified within the exploded graph. */
+
+struct infinite_loop
+{
+ infinite_loop (const exploded_node &enode,
+ location_t loc,
+ std::vector<const exploded_edge *> eedges,
+ logger *logger)
+ : m_enode (enode),
+ m_loc (loc),
+ m_eedge_vec (eedges)
+ {
+ LOG_SCOPE (logger);
+ if (logger)
+ {
+ logger->start_log_line ();
+ logger->log_partial ("infinite loop: EN: %i", m_enode.m_index);
+ for (auto eedge : m_eedge_vec)
+ {
+ logger->log_partial (" ->");
+ if (const superedge *sedge = eedge->m_sedge)
+ {
+ sedge->dump_label_to_pp (logger->get_printer (), false);
+ }
+ logger->log_partial (" EN: %i", eedge->m_dest->m_index);
+ }
+ logger->end_log_line ();
+ }
+ }
+
+ bool
+ operator== (const infinite_loop &other) const
+ {
+ /* Compare underlying supernode, rather than enodes, so that
+ we don't get duplicates in functions that are called from
+ elsewhere. */
+ return (m_enode.get_supernode () == other.m_enode.get_supernode ()
+ && m_loc == other.m_loc);
+ }
+
+ const exploded_node &m_enode;
+ location_t m_loc;
+ std::vector<const exploded_edge *> m_eedge_vec;
+};
+
+/* A custom subclass of start_cfg_edge_event that rewords the
+ message to indicate that the CFG edge is *always* taken on
+ subsequent iterations, assuming it's been taken once. */
+
+class perpetual_start_cfg_edge_event : public start_cfg_edge_event
+{
+public:
+ perpetual_start_cfg_edge_event (const exploded_edge &eedge,
+ const event_loc_info &loc_info)
+ : start_cfg_edge_event (eedge, loc_info)
+ {
+ }
+
+ label_text get_desc (bool can_colorize) const final override
+ {
+ 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,
+ "%s: always following %qs branch...",
+ cond_desc.get (), edge_desc.get ());
+ else
+ return make_label_text
+ (can_colorize,
+ "if it ever follows %qs branch, it will always do so...",
+ edge_desc.get ());
+ }
+ }
+ return start_cfg_edge_event::get_desc (can_colorize);
+ }
+};
+
+/* A subclass of pending_diagnostic for complaining about suspected
+ infinite loops. */
+
+class infinite_loop_diagnostic
+: public pending_diagnostic_subclass<infinite_loop_diagnostic>
+{
+public:
+ infinite_loop_diagnostic (std::unique_ptr<infinite_loop> inf_loop)
+ : m_inf_loop (std::move (inf_loop))
+ {
+ gcc_assert (m_inf_loop != nullptr);
+ }
+
+ const char *get_kind () const final override
+ {
+ return "infinite_loop_diagnostic";
+ }
+
+ bool operator== (const infinite_loop_diagnostic &other) const
+ {
+ return *m_inf_loop == *other.m_inf_loop;
+ }
+
+ int get_controlling_option () const final override
+ {
+ return OPT_Wanalyzer_infinite_loop;
+ }
+
+ bool emit (rich_location *rich_loc, logger *) final override
+ {
+ /* "CWE-835: Loop with Unreachable Exit Condition ('Infinite Loop')". */
+ diagnostic_metadata m;
+ m.add_cwe (835);
+ return warning_meta (rich_loc, m, get_controlling_option (),
+ "infinite loop");
+ }
+
+ bool maybe_add_custom_events_for_superedge (const exploded_edge &,
+ checker_path *)
+ final override
+ {
+ /* Don't add any regular events; instead we add them after pruning as
+ part of the "final" warning. */
+ return true;
+ }
+
+ label_text describe_final_event (const evdesc::final_event &ev) final override
+ {
+ return ev.formatted_print ("infinite loop here");
+ }
+
+ /* Customize the location where the warning_event appears. */
+ void add_final_event (const state_machine *,
+ const exploded_node *enode,
+ const gimple *,
+ tree,
+ state_machine::state_t,
+ checker_path *emission_path) final override
+ {
+ emission_path->add_event
+ (make_unique<warning_event>
+ (event_loc_info (m_inf_loop->m_loc,
+ enode->get_function ()->decl,
+ enode->get_stack_depth ()),
+ enode,
+ NULL, NULL, NULL));
+
+ logger *logger = emission_path->get_logger ();
+
+ /* EMISSION_PATH has the path to the entry of the infinite loop.
+ Add extra edges showing the loop itself. */
+ for (auto eedge : m_inf_loop->m_eedge_vec)
+ {
+ if (logger)
+ logger->log ("EN: %i -> EN: %i",
+ eedge->m_src->m_index,
+ eedge->m_dest->m_index);
+ if (!eedge->m_sedge)
+ continue;
+
+ const cfg_superedge *cfg_sedge
+ = eedge->m_sedge->dyn_cast_cfg_superedge ();
+ if (!cfg_sedge)
+ continue;
+
+ const exploded_node *src_node = eedge->m_src;
+ const program_point &src_point = src_node->get_point ();
+ const exploded_node *dst_node = eedge->m_dest;
+ const program_point &dst_point = dst_node->get_point ();
+ const int src_stack_depth = src_point.get_stack_depth ();
+ const int dst_stack_depth = dst_point.get_stack_depth ();
+ const gimple *last_stmt = src_point.get_supernode ()->get_last_stmt ();
+
+ event_loc_info loc_info_from
+ (last_stmt ? last_stmt->location : cfg_sedge->get_goto_locus (),
+ src_point.get_fndecl (),
+ src_stack_depth);
+ event_loc_info loc_info_to
+ (dst_point.get_supernode ()->get_start_location (),
+ dst_point.get_fndecl (),
+ dst_stack_depth);
+
+ if (const switch_cfg_superedge *switch_cfg_sedge
+ = cfg_sedge->dyn_cast_switch_cfg_superedge ())
+ {
+ if (switch_cfg_sedge->implicitly_created_default_p ())
+ {
+ emission_path->add_event
+ (make_unique<perpetual_start_cfg_edge_event> (*eedge,
+ loc_info_from));
+ emission_path->add_event
+ (make_unique<end_cfg_edge_event>
+ (*eedge,
+ loc_info_to));
+ }
+ }
+
+ if (cfg_sedge->true_value_p ())
+ {
+ emission_path->add_event
+ (make_unique<perpetual_start_cfg_edge_event> (*eedge,
+ loc_info_from));
+ emission_path->add_event
+ (make_unique<end_cfg_edge_event>
+ (*eedge,
+ loc_info_to));
+ }
+ else if (cfg_sedge->false_value_p ())
+ {
+ emission_path->add_event
+ (make_unique<perpetual_start_cfg_edge_event> (*eedge,
+ loc_info_from));
+ emission_path->add_event
+ (make_unique<end_cfg_edge_event>
+ (*eedge,
+ loc_info_to));
+ }
+ else if (cfg_sedge->back_edge_p ())
+ {
+ emission_path->add_event
+ (make_unique<precanned_custom_event>
+ (loc_info_from, "looping back..."));
+ emission_path->add_event
+ (make_unique<end_cfg_edge_event>
+ (*eedge,
+ loc_info_to));
+ }
+ }
+ }
+
+private:
+ std::unique_ptr<infinite_loop> m_inf_loop;
+};
+
+/* If ENODE has an in-edge corresponding to a CFG backedge, return that
+ exploded in-edge.
+ Otherwise, return nullptr. */
+
+static const exploded_edge *
+get_in_edge_back_edge (const exploded_node &enode)
+{
+ for (auto in_edge : enode.m_preds)
+ {
+ const superedge *sedge = in_edge->m_sedge;
+ if (!sedge)
+ continue;
+ const cfg_superedge *cfg_sedge = sedge->dyn_cast_cfg_superedge ();
+ if (!cfg_sedge)
+ continue;
+ if (cfg_sedge->back_edge_p ())
+ return in_edge;
+ }
+ return nullptr;
+}
+
+/* Subclass of region_model_context that rejects conditional branches that
+ aren't known for definite. */
+
+class infinite_loop_checking_context : public noop_region_model_context
+{
+public:
+ infinite_loop_checking_context () : m_unusable (false) {}
+
+ bool checking_for_infinite_loop_p () const override { return true; }
+ void on_unusable_in_infinite_loop () override { m_unusable = true; }
+
+ bool unusable_p () const { return m_unusable; }
+
+private:
+ bool m_unusable;
+};
+
+/* Determine if an infinite loop starts at ENODE.
+ Return the loop if it is found, nullptr otherwise.
+
+ Look for cycles in the exploded graph in which:
+ - no externally visible work occurs
+ - no escape from the cycle
+ - the program state is "sufficiently concrete" at each step:
+ - no unknown activity could be occurring
+ - the worklist was fully drained for each enode in the cycle
+ i.e. every enode in the cycle is processed. */
+
+static std::unique_ptr<infinite_loop>
+starts_infinite_loop_p (const exploded_node &enode,
+ const exploded_graph &eg,
+ logger *logger)
+{
+ LOG_FUNC_1 (logger, "considering EN: %i", enode.m_index);
+
+ /* Only consider enodes that have a CFG back edge as an in-edge. */
+ if (const exploded_edge *back_edge = get_in_edge_back_edge (enode))
+ {
+ if (logger)
+ logger->log ("got backedge from EN: %i",
+ back_edge->m_src->m_index);
+ }
+ else
+ {
+ if (logger)
+ logger->log ("rejecting: no backedge in in-edges");
+ return nullptr;
+ }
+
+ /* Support for dumping an .infinite-loop.dot file visualizing the
+ traversal for this enode. */
+ std::unique_ptr<feasible_graph> fg;
+ feasible_node *curr_fnode = nullptr;
+
+ if (flag_dump_analyzer_infinite_loop)
+ fg = ::make_unique<feasible_graph> ();
+
+ location_t first_loc = UNKNOWN_LOCATION;
+ const exploded_node *iter = &enode;
+ feasibility_state state (*enode.get_state ().m_region_model,
+ eg.get_supergraph ());
+
+ if (fg)
+ curr_fnode = fg->add_node (&enode, state, 0);
+
+ hash_set<const exploded_node *> visited;
+ std::vector<const exploded_edge *> eedges;
+ while (1)
+ {
+ if (logger)
+ logger->log ("iter: EN: %i", iter->m_index);
+ /* Analysis bailed out before processing this node. */
+ if (iter->get_status () == exploded_node::STATUS_WORKLIST)
+ {
+ if (logger)
+ logger->log ("rejecting: EN: %i is still in worklist",
+ iter->m_index);
+ return nullptr;
+ }
+ if (visited.contains (iter))
+ {
+ /* We've looped back on ourselves. ENODE is in the loop
+ itself if ENODE is the first place we looped back,
+ as opposed to being on a path to a loop. */
+ if (iter == &enode)
+ {
+ if (logger)
+ logger->log ("accepting: looped back to EN: %i",
+ iter->m_index);
+ if (fg)
+ {
+ auto_timevar tv (TV_ANALYZER_DUMP);
+ pretty_printer pp;
+ pp_printf (&pp, "%s.en%i.infinite-loop.dot",
+ dump_base_name, enode.m_index);
+ char *filename = xstrdup (pp_formatted_text (&pp));
+ feasible_graph::dump_args_t dump_args (eg);
+ fg->dump_dot (filename, nullptr, dump_args);
+ free (filename);
+ }
+ return ::make_unique<infinite_loop> (enode,
+ first_loc,
+ eedges,
+ logger);
+ }
+ else
+ {
+ if (logger)
+ logger->log ("rejecting: looped back to EN: %i, not to EN: %i",
+ iter->m_index, enode.m_index);
+ return nullptr;
+ }
+ }
+ visited.add (iter);
+ if (first_loc == UNKNOWN_LOCATION)
+ {
+ location_t enode_loc = iter->get_point ().get_location ();
+ if (enode_loc != UNKNOWN_LOCATION)
+ first_loc = enode_loc;
+ }
+
+ /* Find the out-edges that are feasible, given the
+ constraints here. */
+ typedef std::pair<feasibility_state, const exploded_edge *> pair_t;
+ std::vector<pair_t> succs;
+ for (auto out_edge : iter->m_succs)
+ {
+ log_scope s (logger, "considering out-edge",
+ "EN:%i -> EN:%i",
+ out_edge->m_src->m_index,
+ out_edge->m_dest->m_index);
+ feasibility_state next_state (state);
+
+ /* Use this context to require edge conditions to be known,
+ rather than be merely "possible". */
+ infinite_loop_checking_context ctxt;
+ if (next_state.maybe_update_for_edge (logger,
+ out_edge,
+ &ctxt,
+ nullptr))
+ succs.push_back (pair_t (next_state, out_edge));
+ if (ctxt.unusable_p ())
+ {
+ /* If we get here, then we have e.g. a gcond where
+ the condition is UNKNOWN, or a condition
+ based on a widening_svalue. Reject such paths. */
+ if (logger)
+ logger->log ("rejecting: unusable");
+ return nullptr;
+ }
+ }
+
+ if (succs.size () != 1)
+ {
+ if (logger)
+ logger->log ("rejecting: %i feasible successors",
+ (int)succs.size ());
+ return nullptr;
+ }
+ const feasibility_state &next_state = succs[0].first;
+ const exploded_edge *out_eedge = succs[0].second;
+ if (out_eedge->could_do_work_p ())
+ {
+ if (logger)
+ logger->log ("rejecting: edge could do work");
+ return nullptr;
+ }
+ if (fg)
+ {
+ feasible_node *next_fnode = fg->add_node (out_eedge->m_dest,
+ next_state,
+ fg->m_nodes.length ());
+ fg->add_edge (new feasible_edge (curr_fnode, next_fnode, out_eedge));
+ curr_fnode = next_fnode;
+ }
+ state = next_state;
+ eedges.push_back (out_eedge);
+ if (first_loc == UNKNOWN_LOCATION)
+ {
+ if (out_eedge->m_sedge)
+ if (::edge cfg_edge = out_eedge->m_sedge->get_any_cfg_edge ())
+ if (cfg_edge->goto_locus > BUILTINS_LOCATION)
+ first_loc = cfg_edge->goto_locus;
+ }
+ iter = out_eedge->m_dest;
+ }
+}
+
+/* Implementation of -Wanalyzer-infinite-loop. */
+
+void
+exploded_graph::detect_infinite_loops ()
+{
+ LOG_FUNC (get_logger ());
+ auto_timevar tv (TV_ANALYZER_INFINITE_LOOPS);
+
+ /* Track all enodes we've warned for; both the loop entrypoints
+ and all the enodes within those loops. */
+ hash_set<const exploded_node *> warned_for;
+
+ for (auto enode : m_nodes)
+ {
+ if (get_logger ())
+ get_logger ()->log ("visited: %i out of %i",
+ (int)warned_for.elements (), m_nodes.length ());
+
+ /* Only warn about the first enode we encounter in each cycle. */
+ if (warned_for.contains(enode))
+ continue;
+
+ if (std::unique_ptr<infinite_loop> inf_loop
+ = starts_infinite_loop_p (*enode, *this, get_logger ()))
+ {
+ const supernode *snode = enode->get_supernode ();
+
+ if (get_logger ())
+ get_logger ()->log ("EN: %i from starts_infinite_loop_p",
+ enode->m_index);
+
+ for (auto iter : inf_loop->m_eedge_vec)
+ warned_for.add (iter->m_src);
+ gcc_assert (warned_for.contains(enode));
+
+ if (inf_loop->m_loc == UNKNOWN_LOCATION)
+ {
+ if (get_logger ())
+ get_logger ()->log
+ ("no location available for reporting infinite loop");
+ continue;
+ }
+
+ pending_location ploc (enode, snode, inf_loop->m_loc);
+ auto d
+ = ::make_unique<infinite_loop_diagnostic> (std::move (inf_loop));
+ get_diagnostic_manager ().add_diagnostic (ploc, std::move (d));
+ }
+ }
+}