1 /* Detection of infinite recursion.
2 Copyright (C) 2022-2025 Free Software Foundation, Inc.
3 Contributed by David Malcolm <dmalcolm@redhat.com>.
5 This file is part of GCC.
7 GCC is free software; you can redistribute it and/or modify it
8 under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 3, or (at your option)
12 GCC is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with GCC; see the file COPYING3. If not see
19 <http://www.gnu.org/licenses/>. */
22 #define INCLUDE_VECTOR
24 #include "coretypes.h"
26 #include "fold-const.h"
27 #include "gcc-rich-location.h"
28 #include "alloc-pool.h"
29 #include "fibonacci_heap.h"
30 #include "shortest-paths.h"
31 #include "diagnostic-core.h"
32 #include "diagnostic-event-id.h"
33 #include "diagnostic-path.h"
35 #include "pretty-print.h"
39 #include "ordered-hash-map.h"
42 #include "analyzer/analyzer.h"
43 #include "analyzer/analyzer-logging.h"
44 #include "analyzer/call-string.h"
45 #include "analyzer/program-point.h"
46 #include "analyzer/store.h"
47 #include "analyzer/region-model.h"
48 #include "analyzer/constraint-manager.h"
49 #include "analyzer/sm.h"
50 #include "analyzer/pending-diagnostic.h"
51 #include "analyzer/diagnostic-manager.h"
53 #include "basic-block.h"
55 #include "gimple-iterator.h"
56 #include "gimple-pretty-print.h"
59 #include "analyzer/supergraph.h"
60 #include "analyzer/program-state.h"
61 #include "analyzer/exploded-graph.h"
62 #include "make-unique.h"
63 #include "analyzer/checker-path.h"
64 #include "analyzer/feasible-graph.h"
65 #include "diagnostic-format-sarif.h"
67 /* A subclass of pending_diagnostic for complaining about suspected
68 infinite recursion. */
70 class infinite_recursion_diagnostic
71 : public pending_diagnostic_subclass
<infinite_recursion_diagnostic
>
74 infinite_recursion_diagnostic (const exploded_node
*prev_entry_enode
,
75 const exploded_node
*new_entry_enode
,
77 : m_prev_entry_enode (prev_entry_enode
),
78 m_new_entry_enode (new_entry_enode
),
79 m_callee_fndecl (callee_fndecl
),
80 m_prev_entry_event (NULL
)
83 const char *get_kind () const final override
85 return "infinite_recursion_diagnostic";
88 bool operator== (const infinite_recursion_diagnostic
&other
) const
90 return m_callee_fndecl
== other
.m_callee_fndecl
;
93 int get_controlling_option () const final override
95 return OPT_Wanalyzer_infinite_recursion
;
98 bool emit (diagnostic_emission_context
&ctxt
) final override
100 /* "CWE-674: Uncontrolled Recursion". */
102 return ctxt
.warn ("infinite recursion");
106 describe_final_event (pretty_printer
&pp
,
107 const evdesc::final_event
&) final override
109 const int frames_consumed
= (m_new_entry_enode
->get_stack_depth ()
110 - m_prev_entry_enode
->get_stack_depth ());
111 if (frames_consumed
> 1)
113 "apparently infinite chain of mutually-recursive function"
114 " calls, consuming %i stack frames per recursion",
117 pp_string (&pp
, "apparently infinite recursion");
122 add_function_entry_event (const exploded_edge
&eedge
,
123 checker_path
*emission_path
) final override
125 /* Subclass of function_entry_event for use when reporting both
126 the initial and subsequent entries to the function of interest,
127 allowing for cross-referencing the first event in the description
129 class recursive_function_entry_event
: public function_entry_event
132 recursive_function_entry_event (const program_point
&dst_point
,
133 const infinite_recursion_diagnostic
&pd
,
135 : function_entry_event (dst_point
),
142 print_desc (pretty_printer
&pp
) const final override
146 if (m_pd
.m_prev_entry_event
147 && m_pd
.m_prev_entry_event
->get_id_ptr ()->known_p ())
149 "recursive entry to %qE; previously entered at %@",
151 m_pd
.m_prev_entry_event
->get_id_ptr ());
154 "recursive entry to %qE",
159 "initial entry to %qE",
164 const infinite_recursion_diagnostic
&m_pd
;
167 const exploded_node
*dst_node
= eedge
.m_dest
;
168 const program_point
&dst_point
= dst_node
->get_point ();
169 if (eedge
.m_dest
== m_prev_entry_enode
)
171 gcc_assert (m_prev_entry_event
== NULL
);
172 std::unique_ptr
<checker_event
> prev_entry_event
173 = make_unique
<recursive_function_entry_event
> (dst_point
,
175 m_prev_entry_event
= prev_entry_event
.get ();
176 emission_path
->add_event (std::move (prev_entry_event
));
178 else if (eedge
.m_dest
== m_new_entry_enode
)
179 emission_path
->add_event
180 (make_unique
<recursive_function_entry_event
> (dst_point
, *this, true));
182 pending_diagnostic::add_function_entry_event (eedge
, emission_path
);
185 /* Customize the location where the warning_event appears, putting
186 it at the topmost entrypoint to the function. */
187 void add_final_event (const state_machine
*,
188 const exploded_node
*enode
,
189 const event_loc_info
&,
191 state_machine::state_t
,
192 checker_path
*emission_path
) final override
194 gcc_assert (m_new_entry_enode
);
195 emission_path
->add_event
196 (make_unique
<warning_event
>
197 (event_loc_info (m_new_entry_enode
->get_supernode
198 ()->get_start_location (),
200 m_new_entry_enode
->get_stack_depth ()),
202 nullptr, nullptr, nullptr));
205 /* Reject paths in which conjured svalues have affected control flow
206 since m_prev_entry_enode. */
208 bool check_valid_fpath_p (const feasible_node
&final_fnode
,
212 /* Reject paths in which calls with unknown side effects have occurred
213 since m_prev_entry_enode.
214 Find num calls with side effects. Walk backward until we reach the
216 gcc_assert (final_fnode
.get_inner_node () == m_new_entry_enode
);
218 /* FG is actually a tree. Walk backwards from FINAL_FNODE until we
219 reach the prev_entry_enode (or the origin). */
220 const feasible_node
*iter_fnode
= &final_fnode
;
221 while (iter_fnode
->get_inner_node ()->m_index
!= 0)
223 gcc_assert (iter_fnode
->m_preds
.length () == 1);
225 feasible_edge
*pred_fedge
226 = static_cast <feasible_edge
*> (iter_fnode
->m_preds
[0]);
228 /* Determine if conjured svalues have affected control flow
229 since the prev entry node. */
230 if (fedge_uses_conjured_svalue_p (pred_fedge
))
231 /* If so, then reject this diagnostic. */
233 iter_fnode
= static_cast <feasible_node
*> (pred_fedge
->m_src
);
234 if (iter_fnode
->get_inner_node () == m_prev_entry_enode
)
235 /* Accept this diagnostic. */
239 /* We shouldn't get here; if we do, reject the diagnostic. */
244 void maybe_add_sarif_properties (sarif_object
&result_obj
)
247 sarif_property_bag
&props
= result_obj
.get_or_create_properties ();
248 #define PROPERTY_PREFIX "gcc/analyzer/infinite_recursion_diagnostic/"
249 props
.set_integer (PROPERTY_PREFIX
"prev_entry_enode",
250 m_prev_entry_enode
->m_index
);
251 props
.set_integer (PROPERTY_PREFIX
"new_entry_enode",
252 m_new_entry_enode
->m_index
);
253 #undef PROPERTY_PREFIX
257 /* Return true iff control flow along FEDGE was affected by
258 a conjured_svalue. */
259 static bool fedge_uses_conjured_svalue_p (feasible_edge
*fedge
)
261 const exploded_edge
*eedge
= fedge
->get_inner_edge ();
262 const superedge
*sedge
= eedge
->m_sedge
;
265 const cfg_superedge
*cfg_sedge
= sedge
->dyn_cast_cfg_superedge ();
268 const gimple
*last_stmt
= sedge
->m_src
->get_last_stmt ();
272 const feasible_node
*dst_fnode
273 = static_cast<const feasible_node
*> (fedge
->m_dest
);
274 const region_model
&model
= dst_fnode
->get_state ().get_model ();
276 if (const gcond
*cond_stmt
= dyn_cast
<const gcond
*> (last_stmt
))
278 if (expr_uses_conjured_svalue_p (model
, gimple_cond_lhs (cond_stmt
)))
280 if (expr_uses_conjured_svalue_p (model
, gimple_cond_rhs (cond_stmt
)))
283 else if (const gswitch
*switch_stmt
284 = dyn_cast
<const gswitch
*> (last_stmt
))
286 if (expr_uses_conjured_svalue_p (model
,
287 gimple_switch_index (switch_stmt
)))
293 /* Return true iff EXPR is affected by a conjured_svalue. */
294 static bool expr_uses_conjured_svalue_p (const region_model
&model
,
297 class conjured_svalue_finder
: public visitor
300 conjured_svalue_finder () : m_found_conjured_svalues (false)
304 visit_conjured_svalue (const conjured_svalue
*) final override
306 m_found_conjured_svalues
= true;
309 bool m_found_conjured_svalues
;
312 const svalue
*sval
= model
.get_rvalue (expr
, NULL
);
313 conjured_svalue_finder v
;
315 return v
.m_found_conjured_svalues
;
318 const exploded_node
*m_prev_entry_enode
;
319 const exploded_node
*m_new_entry_enode
;
320 tree m_callee_fndecl
;
321 const checker_event
*m_prev_entry_event
;
324 /* Return true iff ENODE is the PK_BEFORE_SUPERNODE at a function
328 is_entrypoint_p (exploded_node
*enode
)
330 /* Look for an entrypoint to a function... */
331 const supernode
*snode
= enode
->get_supernode ();
334 if (!snode
->entry_p ())
336 const program_point
&point
= enode
->get_point ();
337 if (point
.get_kind () != PK_BEFORE_SUPERNODE
)
342 /* Walk backwards through the eg, looking for the first
343 enode we find that's also the entrypoint of the same function. */
346 exploded_graph::find_previous_entry_to (function
*top_of_stack_fun
,
347 exploded_node
*enode
) const
349 auto_vec
<exploded_node
*> worklist
;
350 hash_set
<exploded_node
*> visited
;
353 for (auto in_edge
: enode
->m_preds
)
354 worklist
.safe_push (in_edge
->m_src
);
356 while (worklist
.length () > 0)
358 exploded_node
*iter
= worklist
.pop ();
360 if (is_entrypoint_p (iter
)
361 && iter
->get_function () == top_of_stack_fun
)
364 if (visited
.contains (iter
))
367 for (auto in_edge
: iter
->m_preds
)
368 worklist
.safe_push (in_edge
->m_src
);
375 /* Given BASE_REG within ENCLOSING_FRAME (such as a function parameter),
376 remap it to the equivalent region within EQUIV_PREV_FRAME.
378 For example, given param "n" within frame "foo@3", and equiv prev frame
379 "foo@1", remap it to param "n" within frame "foo@1". */
381 static const region
*
382 remap_enclosing_frame (const region
*base_reg
,
383 const frame_region
*enclosing_frame
,
384 const frame_region
*equiv_prev_frame
,
385 region_model_manager
*mgr
)
387 gcc_assert (base_reg
->get_parent_region () == enclosing_frame
);
388 switch (base_reg
->get_kind ())
391 /* We should only encounter params and varargs at the topmost
397 const var_arg_region
*var_arg_reg
= (const var_arg_region
*)base_reg
;
398 return mgr
->get_var_arg_region (equiv_prev_frame
,
399 var_arg_reg
->get_index ());
403 const decl_region
*decl_reg
= (const decl_region
*)base_reg
;
404 return equiv_prev_frame
->get_region_for_local (mgr
,
405 decl_reg
->get_decl (),
411 /* Return true iff SVAL is unknown, or contains an unknown svalue. */
414 contains_unknown_p (const svalue
*sval
)
416 if (sval
->get_kind () == SK_UNKNOWN
)
418 if (const compound_svalue
*compound_sval
419 = sval
->dyn_cast_compound_svalue ())
420 for (auto iter
: *compound_sval
)
421 if (iter
.second
->get_kind () == SK_UNKNOWN
)
426 /* Subroutine of sufficiently_different_p. Compare the store bindings
427 for BASE_REG within NEW_ENTRY_ENODE and PREV_ENTRY_ENODE.
429 Return true if the state of NEW_ENTRY_ENODE is sufficiently different
430 from PREV_ENTRY_ENODE within BASE_REG to suggest that some variant is
431 being modified, and thus the recursion isn't infinite.
433 Return false if the states for BASE_REG are effectively the same. */
436 sufficiently_different_region_binding_p (exploded_node
*new_entry_enode
,
437 exploded_node
*prev_entry_enode
,
438 const region
*base_reg
)
440 /* Compare the stores of the two enodes. */
441 const region_model
&new_model
442 = *new_entry_enode
->get_state ().m_region_model
;
443 const region_model
&prev_model
444 = *prev_entry_enode
->get_state ().m_region_model
;
446 /* Get the value within the new frame. */
447 const svalue
*new_sval
448 = new_model
.get_store_value (base_reg
, NULL
);
450 /* If any part of the value is UNKNOWN (e.g. due to hitting
451 complexity limits) assume that it differs from the previous
453 if (contains_unknown_p (new_sval
))
456 /* Get the equivalent value within the old enode. */
457 const svalue
*prev_sval
;
459 if (const frame_region
*enclosing_frame
460 = base_reg
->maybe_get_frame_region ())
462 /* We have a binding within a frame in the new entry enode. */
464 /* Consider changes in bindings below the original entry
466 const int old_stack_depth
= prev_entry_enode
->get_stack_depth ();
467 if (enclosing_frame
->get_stack_depth () < old_stack_depth
)
468 prev_sval
= prev_model
.get_store_value (base_reg
, NULL
);
471 /* Ignore bindings within frames below the new entry node. */
472 const int new_stack_depth
= new_entry_enode
->get_stack_depth ();
473 if (enclosing_frame
->get_stack_depth () < new_stack_depth
)
476 /* We have a binding within the frame of the new entry node,
477 presumably a parameter. */
479 /* Get the value within the equivalent frame of
480 the old entrypoint; typically will be the initial_svalue
482 const frame_region
*equiv_prev_frame
483 = prev_model
.get_current_frame ();
484 const region
*equiv_prev_base_reg
485 = remap_enclosing_frame (base_reg
,
488 new_model
.get_manager ());
490 = prev_model
.get_store_value (equiv_prev_base_reg
, NULL
);
494 prev_sval
= prev_model
.get_store_value (base_reg
, NULL
);
496 /* If the prev_sval contains UNKNOWN (e.g. due to hitting complexity limits)
497 assume that it will differ from any new value. */
498 if (contains_unknown_p (prev_sval
))
501 if (new_sval
!= prev_sval
)
507 /* Compare the state of memory at NEW_ENTRY_ENODE and PREV_ENTRY_ENODE,
508 both of which are entrypoints to the same function, where recursion has
511 Return true if the state of NEW_ENTRY_ENODE is sufficiently different
512 from PREV_ENTRY_ENODE to suggest that some variant is being modified,
513 and thus the recursion isn't infinite.
515 Return false if the states are effectively the same, suggesting that
516 the recursion is infinite.
518 For example, consider mutually recursive functions "foo" and "bar".
519 At the entrypoint to a "foo" frame where we've detected recursion,
520 we might have three frames on the stack: the new 'foo'@3, an inner
521 'bar'@2, and the innermost 'foo'@1.
523 (gdb) call enode->dump(m_ext_state)
525 callstring: [(SN: 9 -> SN: 3 in foo), (SN: 5 -> SN: 8 in bar)]
526 before SN: 0 (NULL from-edge)
530 frame (index 2): frame: ‘foo’@3
531 frame (index 1): frame: ‘bar’@2
532 frame (index 0): frame: ‘foo’@1
533 clusters within root region
534 cluster for: (*INIT_VAL(f_4(D)))
535 clusters within frame: ‘bar’@2
536 cluster for: b_2(D): INIT_VAL(f_4(D))
537 clusters within frame: ‘foo’@3
538 cluster for: f_4(D): INIT_VAL(f_4(D))
539 m_called_unknown_fn: FALSE
541 whereas for the previous entry node we'd have just the innermost
544 (gdb) call prev_entry_enode->dump(m_ext_state)
547 before SN: 0 (NULL from-edge)
551 frame (index 0): frame: ‘foo’@1
552 clusters within root region
553 cluster for: (*INIT_VAL(f_4(D)))
554 m_called_unknown_fn: FALSE
556 We want to abstract away frames 1 and 2 in the new entry enode,
557 and compare its frame 3 with the frame 1 in the previous entry
558 enode, and determine if enough state changes between them to
559 rule out infinite recursion. */
562 sufficiently_different_p (exploded_node
*new_entry_enode
,
563 exploded_node
*prev_entry_enode
,
567 gcc_assert (new_entry_enode
);
568 gcc_assert (prev_entry_enode
);
569 gcc_assert (is_entrypoint_p (new_entry_enode
));
570 gcc_assert (is_entrypoint_p (prev_entry_enode
));
572 /* Compare the stores of the two enodes. */
573 const region_model
&new_model
574 = *new_entry_enode
->get_state ().m_region_model
;
575 const store
&new_store
= *new_model
.get_store ();
577 for (auto kv
: new_store
)
579 const region
*base_reg
= kv
.first
;
580 if (sufficiently_different_region_binding_p (new_entry_enode
,
586 /* No significant differences found. */
590 /* Implementation of -Wanalyzer-infinite-recursion.
592 Called when adding ENODE to the graph, after adding its first in-edge.
594 For function entrypoints, see if recursion has occurred, and, if so,
595 check if the state of memory changed between the recursion levels,
596 which would suggest some kind of decreasing variant that leads to
599 For recursive calls where the state of memory is effectively unchanged
600 between recursion levels, warn with -Wanalyzer-infinite-recursion. */
603 exploded_graph::detect_infinite_recursion (exploded_node
*enode
)
605 if (!is_entrypoint_p (enode
))
607 function
*top_of_stack_fun
= enode
->get_function ();
608 gcc_assert (top_of_stack_fun
);
610 /* ....where a call to that function is already in the call string. */
611 const call_string
&call_string
= enode
->get_point ().get_call_string ();
613 if (call_string
.count_occurrences_of_function (top_of_stack_fun
) < 2)
616 tree fndecl
= top_of_stack_fun
->decl
;
618 log_scope
s (get_logger (),
619 "checking for infinite recursion",
620 "considering recursion at EN: %i entering %qE",
621 enode
->m_index
, fndecl
);
623 /* Find enode that's the entrypoint for the previous frame for fndecl
625 exploded_node
*prev_entry_enode
626 = find_previous_entry_to (top_of_stack_fun
, enode
);
627 gcc_assert (prev_entry_enode
);
629 get_logger ()->log ("previous entrypoint to %qE is EN: %i",
630 fndecl
, prev_entry_enode
->m_index
);
632 /* Look for changes to the state of memory between the recursion levels. */
633 if (sufficiently_different_p (enode
, prev_entry_enode
, get_logger ()))
636 /* Otherwise, the state of memory is effectively the same between the two
637 recursion levels; warn. */
639 const supernode
*caller_snode
= call_string
.get_top_of_stack ().m_caller
;
640 const supernode
*snode
= enode
->get_supernode ();
641 gcc_assert (caller_snode
->m_returning_call
);
642 pending_location
ploc (enode
,
644 caller_snode
->m_returning_call
,
646 get_diagnostic_manager ().add_diagnostic
648 make_unique
<infinite_recursion_diagnostic
> (prev_entry_enode
,