blob: a53807c2ddb657ae4810e25800168b9476f8c62f [file] [log] [blame]
/* Detection of infinite loops.
Copyright (C) 2022-2025 Free Software Foundation, Inc.
Contributed by David Malcolm <dmalcolm@redhat.com>.
This file is part of GCC.
GCC is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GCC is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
#include "analyzer/common.h"
#include "cfg.h"
#include "gimple-iterator.h"
#include "gimple-pretty-print.h"
#include "cgraph.h"
#include "digraph.h"
#include "diagnostics/sarif-sink.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 "analyzer/supergraph.h"
#include "analyzer/program-state.h"
#include "analyzer/exploded-graph.h"
#include "analyzer/checker-path.h"
#include "analyzer/feasible-graph.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);
}
std::unique_ptr<json::object>
to_json () const
{
auto loop_obj = std::make_unique<json::object> ();
loop_obj->set_integer ("enode", m_enode.m_index);
auto edge_arr = std::make_unique<json::array> ();
for (auto eedge : m_eedge_vec)
edge_arr->append (eedge->to_json ());
loop_obj->set ("eedges", std::move (edge_arr));
return loop_obj;
}
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)
{
}
void print_desc (pretty_printer &pp) 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 (pp_show_color (&pp));
if (cond_desc.get ())
pp_printf (&pp,
"%s: always following %qs branch...",
cond_desc.get (), edge_desc.get ());
else
pp_printf (&pp,
"if it ever follows %qs branch,"
" it will always do so...",
edge_desc.get ());
}
}
else
return start_cfg_edge_event::print_desc (pp);
}
};
class looping_back_event : public start_cfg_edge_event
{
public:
looping_back_event (const exploded_edge &eedge,
const event_loc_info &loc_info)
: start_cfg_edge_event (eedge, loc_info)
{
}
void print_desc (pretty_printer &pp) const final override
{
pp_string (&pp, "looping back...");
}
};
/* 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 (diagnostic_emission_context &ctxt) final override
{
/* "CWE-835: Loop with Unreachable Exit Condition ('Infinite Loop')". */
ctxt.add_cwe (835);
return ctxt.warn ("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;
}
bool
describe_final_event (pretty_printer &pp,
const evdesc::final_event &) final override
{
pp_string (&pp, "infinite loop here");
return true;
}
/* Customize the location where the warning_event appears. */
void add_final_event (const state_machine *,
const exploded_node *enode,
const event_loc_info &,
tree,
state_machine::state_t,
checker_path *emission_path) final override
{
emission_path->add_event
(std::make_unique<warning_event>
(event_loc_info (m_inf_loop->m_loc,
enode->get_function ()->decl,
enode->get_stack_depth ()),
enode,
nullptr, nullptr, nullptr));
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
(std::make_unique<perpetual_start_cfg_edge_event>
(*eedge,
loc_info_from));
emission_path->add_event
(std::make_unique<end_cfg_edge_event>
(*eedge,
loc_info_to));
}
}
if (cfg_sedge->true_value_p ())
{
emission_path->add_event
(std::make_unique<perpetual_start_cfg_edge_event>
(*eedge,
loc_info_from));
emission_path->add_event
(std::make_unique<end_cfg_edge_event>
(*eedge,
loc_info_to));
}
else if (cfg_sedge->false_value_p ())
{
emission_path->add_event
(std::make_unique<perpetual_start_cfg_edge_event>
(*eedge,
loc_info_from));
emission_path->add_event
(std::make_unique<end_cfg_edge_event>
(*eedge,
loc_info_to));
}
else if (cfg_sedge->back_edge_p ())
{
emission_path->add_event
(std::make_unique<looping_back_event> (*eedge, loc_info_from));
emission_path->add_event
(std::make_unique<end_cfg_edge_event>
(*eedge,
loc_info_to));
}
}
}
void
maybe_add_sarif_properties (diagnostics::sarif_object &result_obj)
const final override
{
auto &props = result_obj.get_or_create_properties ();
#define PROPERTY_PREFIX "gcc/analyzer/infinite_loop_diagnostic/"
props.set (PROPERTY_PREFIX "inf_loop", m_inf_loop->to_json ());
#undef PROPERTY_PREFIX
}
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 = std::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 std::make_unique<infinite_loop> (enode,
first_loc,
std::move (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
= std::make_unique<infinite_loop_diagnostic> (std::move (inf_loop));
get_diagnostic_manager ().add_diagnostic (ploc, std::move (d));
}
}
}