]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
analyzer: new warning: -Wanalyzer-infinite-recursion [PR106147]
authorDavid Malcolm <dmalcolm@redhat.com>
Fri, 11 Nov 2022 20:58:40 +0000 (15:58 -0500)
committerDavid Malcolm <dmalcolm@redhat.com>
Fri, 11 Nov 2022 20:58:40 +0000 (15:58 -0500)
This patch adds a new -Wanalyzer-infinite-recursion warning to
-fanalyzer, which complains about certain cases of infinite recursion.

Specifically, when it detects recursion during its symbolic execution
of the user's code, it compares the state of memory to that at the
previous level of recursion, and if nothing appears to have effectively
changed, it issues a warning.

Unlike the middle-end warning -Winfinite-recursion (added by Martin
Sebor in GCC 12; r12-5483-g30ba058f77eedf), the analyzer warning
complains if there exists an interprocedural path in which recursion
occurs in which memory has not changed, whereas -Winfinite-recursion
complains if *every* intraprocedural path through the function leads to
a self-call.

Hence the warnings complement each other: there's some overlap, but each
also catches issues that the other misses.

For example, the new warning complains about a guarded recursion in
which the guard is passed unchanged:

void test_guarded (int flag)
{
  if (flag)
    test_guarded (flag);
}

t.c: In function 'test_guarded':
t.c:4:5: warning: infinite recursion [CWE-674] [-Wanalyzer-infinite-recursion]
    4 |     test_guarded (flag);
      |     ^~~~~~~~~~~~~~~~~~~
  'test_guarded': events 1-4
    |
    |    1 | void test_guarded (int flag)
    |      |      ^~~~~~~~~~~~
    |      |      |
    |      |      (1) initial entry to 'test_guarded'
    |    2 | {
    |    3 |   if (flag)
    |      |      ~
    |      |      |
    |      |      (2) following 'true' branch (when 'flag != 0')...
    |    4 |     test_guarded (flag);
    |      |     ~~~~~~~~~~~~~~~~~~~
    |      |     |
    |      |     (3) ...to here
    |      |     (4) calling 'test_guarded' from 'test_guarded'
    |
    +--> 'test_guarded': events 5-6
           |
           |    1 | void test_guarded (int flag)
           |      |      ^~~~~~~~~~~~
           |      |      |
           |      |      (5) recursive entry to 'test_guarded'; previously entered at (1)
           |      |      (6) apparently infinite recursion
           |

whereas the existing warning doesn't complain, since when "flag" is
false the function doesn't recurse.

The new warning doesn't trigger for e.g.:

  void test_param_variant (int depth)
  {
    if (depth > 0)
      test_param_variant (depth - 1);
  }

on the grounds that "depth" is changing, and appears to be a variant
that enforces termination of the recursion.

gcc/ChangeLog:
PR analyzer/106147
* Makefile.in (ANALYZER_OBJS): Add analyzer/infinite-recursion.o.

gcc/analyzer/ChangeLog:
PR analyzer/106147
* analyzer.opt (Wanalyzer-infinite-recursion): New.
* call-string.cc (call_string::count_occurrences_of_function):
New.
* call-string.h (call_string::count_occurrences_of_function): New
decl.
* checker-path.cc (function_entry_event::function_entry_event):
New ctor.
(checker_path::add_final_event): Delete.
* checker-path.h (function_entry_event::function_entry_event): New
ctor.
(function_entry_event::get_desc): Drop "final".
(checker_path::add_final_event): Delete.
* diagnostic-manager.cc
(diagnostic_manager::emit_saved_diagnostic): Create the final
event via a new pending_diagnostic::add_final_event vfunc, rather
than checker_path::add_final_event.
(diagnostic_manager::add_events_for_eedge): Create function entry
events via a new pending_diagnostic::add_function_entry_event
vfunc.
* engine.cc (exploded_graph::process_node): When creating a new
PK_BEFORE_SUPERNODE node, call
exploded_graph::detect_infinite_recursion on it after adding the
in-edge.
* exploded-graph.h (exploded_graph::detect_infinite_recursion):
New decl.
(exploded_graph::find_previous_entry_to): New decl.
* infinite-recursion.cc: New file.
* pending-diagnostic.cc
(pending_diagnostic::add_function_entry_event): New.
(pending_diagnostic::add_final_event): New.
* pending-diagnostic.h
(pending_diagnostic::add_function_entry_event): New vfunc.
(pending_diagnostic::add_final_event): New vfunc.

gcc/ChangeLog:
PR analyzer/106147
* doc/gcc/gcc-command-options/options-that-control-static-analysis.rst:
Add -Wanalyzer-infinite-recursion.
* doc/gcc/gcc-command-options/options-to-request-or-suppress-warnings.rst
(-Winfinite-recursion): Mention -Wanalyzer-infinite-recursion.

gcc/testsuite/ChangeLog:
PR analyzer/106147
* g++.dg/analyzer/infinite-recursion-1.C: New test.
* g++.dg/analyzer/infinite-recursion-2.C: New test, copied from
g++.dg/warn/Winfinite-recursion-2.C.
* g++.dg/analyzer/infinite-recursion-3.C: New test, adapted from
g++.dg/warn/Winfinite-recursion-3.C.
* gcc.dg/analyzer/infinite-recursion-2.c: New test.
* gcc.dg/analyzer/infinite-recursion-3.c: New test.
* gcc.dg/analyzer/infinite-recursion-4-limited-buggy.c: New test.
* gcc.dg/analyzer/infinite-recursion-4-limited.c: New test.
* gcc.dg/analyzer/infinite-recursion-4-unlimited-buggy.c: New test.
* gcc.dg/analyzer/infinite-recursion-4-unlimited.c: New test.
* gcc.dg/analyzer/infinite-recursion-5.c: New test, adapted from
gcc.dg/Winfinite-recursion.c.
* gcc.dg/analyzer/infinite-recursion-alloca.c: New test.
* gcc.dg/analyzer/infinite-recursion-inlining.c: New test.
* gcc.dg/analyzer/infinite-recursion-multiline-1.c: New test.
* gcc.dg/analyzer/infinite-recursion-multiline-2.c: New test.
* gcc.dg/analyzer/infinite-recursion-variadic.c: New test.
* gcc.dg/analyzer/infinite-recursion.c: Add dg-warning directives
where infinite recursions occur.
* gcc.dg/analyzer/malloc-ipa-12.c: Likewise.
* gcc.dg/analyzer/pr105365.c: Likewise.
* gcc.dg/analyzer/pr105366.c: Likewise.
* gcc.dg/analyzer/pr97029.c: Likewise.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
34 files changed:
gcc/Makefile.in
gcc/analyzer/analyzer.opt
gcc/analyzer/call-string.cc
gcc/analyzer/call-string.h
gcc/analyzer/checker-path.cc
gcc/analyzer/checker-path.h
gcc/analyzer/diagnostic-manager.cc
gcc/analyzer/engine.cc
gcc/analyzer/exploded-graph.h
gcc/analyzer/infinite-recursion.cc [new file with mode: 0644]
gcc/analyzer/pending-diagnostic.cc
gcc/analyzer/pending-diagnostic.h
gcc/doc/gcc/gcc-command-options/options-that-control-static-analysis.rst
gcc/doc/gcc/gcc-command-options/options-to-request-or-suppress-warnings.rst
gcc/testsuite/g++.dg/analyzer/infinite-recursion-1.C [new file with mode: 0644]
gcc/testsuite/g++.dg/analyzer/infinite-recursion-2.C [new file with mode: 0644]
gcc/testsuite/g++.dg/analyzer/infinite-recursion-3.C [new file with mode: 0644]
gcc/testsuite/gcc.dg/analyzer/infinite-recursion-2.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/analyzer/infinite-recursion-3.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/analyzer/infinite-recursion-4-limited-buggy.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/analyzer/infinite-recursion-4-limited.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/analyzer/infinite-recursion-4-unlimited-buggy.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/analyzer/infinite-recursion-4-unlimited.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/analyzer/infinite-recursion-5.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/analyzer/infinite-recursion-alloca.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/analyzer/infinite-recursion-inlining.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/analyzer/infinite-recursion-multiline-1.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/analyzer/infinite-recursion-multiline-2.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/analyzer/infinite-recursion-variadic.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/analyzer/infinite-recursion.c
gcc/testsuite/gcc.dg/analyzer/malloc-ipa-12.c
gcc/testsuite/gcc.dg/analyzer/pr105365.c
gcc/testsuite/gcc.dg/analyzer/pr105366.c
gcc/testsuite/gcc.dg/analyzer/pr97029.c

index 4982012f5b2912cf887071ad894b407653e2f936..246a85a167741ce81d1697507aba7df4d6e23d14 100644 (file)
@@ -1250,6 +1250,7 @@ ANALYZER_OBJS = \
        analyzer/engine.o \
        analyzer/feasible-graph.o \
        analyzer/function-set.o \
+       analyzer/infinite-recursion.o \
        analyzer/known-function-manager.o \
        analyzer/pending-diagnostic.o \
        analyzer/program-point.o \
index 58ad3262ebfaa663efed34e6187931d20fb2b283..518a5d422ffe24cdd7965005f24e0fd200f2432d 100644 (file)
@@ -110,6 +110,10 @@ Wanalyzer-imprecise-fp-arithmetic
 Common Var(warn_analyzer_imprecise_fp_arithmetic) Init(1) Warning
 Warn about code paths in which floating-point arithmetic is used in locations where precise computation is needed.
 
+Wanalyzer-infinite-recursion
+Common Var(warn_analyzer_infinite_recursion) Init(1) Warning
+Warn about code paths which appear to lead to infinite recursion.
+
 Wanalyzer-jump-through-null
 Common Var(warn_analyzer_jump_through_null) Init(1) Warning
 Warn about code paths in which a NULL function pointer is called.
index 5caf92155b9cd6d8953d78bfed524405238e55e9..d78ae81bd7db091ce94493066e810518e9b9def2 100644 (file)
@@ -170,6 +170,22 @@ call_string::calc_recursion_depth () const
   return result;
 }
 
+/* Count the number of times FUN appears in the string.  */
+
+int
+call_string::count_occurrences_of_function (function *fun) const
+{
+  int result = 0;
+  for (const call_string::element_t &e : m_elements)
+    {
+      if (e.get_callee_function () == fun)
+       result++;
+      if (e.get_caller_function () == fun)
+       result++;
+    }
+  return result;
+}
+
 /* Comparator for call strings.
    This implements a version of lexicographical order.
    Return negative if A is before B.
index c3cea9032770c1149f5e0b64bdf5e88c48f8a4c5..d97ff84ce779cb9623856c99a092540f9e232e1f 100644 (file)
@@ -105,6 +105,8 @@ public:
     return m_elements[m_elements.length () - 1];
   }
 
+  int count_occurrences_of_function (function *) const;
+
   void validate () const;
 
 private:
index ffab91c26ff09499d50da1b2c053376dfe19ded5..39de7453f51a176e70434fcc6f7ee6e58b97d297 100644 (file)
@@ -377,6 +377,14 @@ region_creation_event::get_desc (bool can_colorize) const
 
 /* 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.
 
@@ -1304,21 +1312,6 @@ checker_path::add_region_creation_events (const region *reg,
                                                   loc, fndecl, depth));
 }
 
-/* Add a warning_event to the end of this path.  */
-
-void
-checker_path::add_final_event (const state_machine *sm,
-                              const exploded_node *enode, const gimple *stmt,
-                              tree var, state_machine::state_t state)
-{
-  add_event
-    (make_unique<warning_event> (get_stmt_location (stmt,
-                                                   enode->get_function ()),
-                                enode->get_function ()->decl,
-                                enode->get_stack_depth (),
-                                sm, var, state));
-}
-
 void
 checker_path::fixup_locations (pending_diagnostic *pd)
 {
index 46f4875f541497a7ca8e97022bda82426ecf3cfa..53e6bff03ecd583a8ea6e177d5f7c0839dacee1f 100644 (file)
@@ -260,7 +260,9 @@ public:
   {
   }
 
-  label_text get_desc (bool can_colorize) const final override;
+  function_entry_event (const program_point &dst_point);
+
+  label_text get_desc (bool can_colorize) const override;
   meaning get_meaning () const override;
 
   bool is_function_entry_p () const final override { return true; }
@@ -660,10 +662,6 @@ public:
                                   tree fndecl, int depth,
                                   bool debug);
 
-  void add_final_event (const state_machine *sm,
-                       const exploded_node *enode, const gimple *stmt,
-                       tree var, state_machine::state_t state);
-
   /* After all event-pruning, a hook for notifying each event what
      its ID will be.  The events are notified in order, allowing
      for later events to refer to the IDs of earlier events in
index f82dd3a434aee71d0e6532c17597219eba207486..9b319c3a3f58f2cb6685147b905fa5f68e84f4c0 100644 (file)
@@ -1368,8 +1368,8 @@ diagnostic_manager::emit_saved_diagnostic (const exploded_graph &eg,
      We use the final enode from the epath, which might be different from
      the sd.m_enode, as the dedupe code doesn't care about enodes, just
      snodes.  */
-  emission_path.add_final_event (sd.m_sm, epath->get_final_enode (), sd.m_stmt,
-                                sd.m_var, sd.m_state);
+  sd.m_d->add_final_event (sd.m_sm, epath->get_final_enode (), sd.m_stmt,
+                          sd.m_var, sd.m_state, &emission_path);
 
   /* The "final" event might not be final; if the saved_diagnostic has a
      trailing eedge stashed, add any events for it.  This is for use
@@ -1922,11 +1922,8 @@ diagnostic_manager::add_events_for_eedge (const path_builder &pb,
       /* Add function entry events.  */
       if (dst_point.get_supernode ()->entry_p ())
        {
-         emission_path->add_event
-           (make_unique<function_entry_event>
-            (dst_point.get_supernode ()->get_start_location (),
-             dst_point.get_fndecl (),
-             dst_stack_depth));
+         pb.get_pending_diagnostic ()->add_function_entry_event
+           (eedge, emission_path);
          /* Create region_creation_events for on-stack regions within
             this frame.  */
          if (interest)
index fc90b49d0413708e8425efd9a96507f825003365..d0595ef0d07768e875248306bacd33935a6cea4c 100644 (file)
@@ -4276,7 +4276,12 @@ exploded_graph::process_node (exploded_node *node)
            exploded_node *next = get_or_create_node (next_point, next_state,
                                                      node);
            if (next)
-             add_edge (node, next, succ);
+             {
+               add_edge (node, next, succ);
+
+               /* We might have a function entrypoint.  */
+               detect_infinite_recursion (next);
+             }
          }
 
        /* Return from the calls which doesn't have a return superedge.
index de927e6c1b7613c0ac910cf3e309485f695a3a45..a4cbc8f688a2bd878110e80213094f8f5db369e7 100644 (file)
@@ -851,6 +851,11 @@ public:
 
   void on_escaped_function (tree fndecl);
 
+  /* In infinite-recursion.cc */
+  void detect_infinite_recursion (exploded_node *enode);
+  exploded_node *find_previous_entry_to (function *top_of_stack_fun,
+                                        exploded_node *enode) const;
+
 private:
   void print_bar_charts (pretty_printer *pp) const;
 
diff --git a/gcc/analyzer/infinite-recursion.cc b/gcc/analyzer/infinite-recursion.cc
new file mode 100644 (file)
index 0000000..7055926
--- /dev/null
@@ -0,0 +1,481 @@
+/* Detection of infinite recursion.
+   Copyright (C) 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 "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 "make-unique.h"
+#include "analyzer/checker-path.h"
+
+/* A subclass of pending_diagnostic for complaining about suspected
+   infinite recursion.  */
+
+class infinite_recursion_diagnostic
+: public pending_diagnostic_subclass<infinite_recursion_diagnostic>
+{
+public:
+  infinite_recursion_diagnostic (const exploded_node *prev_entry_enode,
+                                const exploded_node *new_entry_enode,
+                                tree callee_fndecl)
+  : m_prev_entry_enode (prev_entry_enode),
+    m_new_entry_enode (new_entry_enode),
+    m_callee_fndecl (callee_fndecl),
+    m_prev_entry_event (NULL)
+  {}
+
+  const char *get_kind () const final override
+  {
+    return "infinite_recursion_diagnostic";
+  }
+
+  bool operator== (const infinite_recursion_diagnostic &other) const
+  {
+    return m_callee_fndecl == other.m_callee_fndecl;
+  }
+
+  int get_controlling_option () const final override
+  {
+    return OPT_Wanalyzer_infinite_recursion;
+  }
+
+  bool emit (rich_location *rich_loc) final override
+  {
+    /* "CWE-674: Uncontrolled Recursion".  */
+    diagnostic_metadata m;
+    m.add_cwe (674);
+    return warning_meta (rich_loc, m, get_controlling_option (),
+                        "infinite recursion");
+  }
+
+  label_text describe_final_event (const evdesc::final_event &ev) final override
+  {
+    const int frames_consumed = (m_new_entry_enode->get_stack_depth ()
+                                - m_prev_entry_enode->get_stack_depth ());
+    if (frames_consumed > 1)
+      return ev.formatted_print
+       ("apparently infinite chain of mutually-recursive function calls,"
+        " consuming %i stack frames per recursion",
+        frames_consumed);
+    else
+      return ev.formatted_print ("apparently infinite recursion");
+  }
+
+  void
+  add_function_entry_event (const exploded_edge &eedge,
+                           checker_path *emission_path) final override
+  {
+    /* Subclass of function_entry_event for use when reporting both
+       the initial and subsequent entries to the function of interest,
+       allowing for cross-referencing the first event in the description
+       of the second.  */
+    class recursive_function_entry_event : public function_entry_event
+    {
+    public:
+      recursive_function_entry_event (const program_point &dst_point,
+                                     const infinite_recursion_diagnostic &pd,
+                                     bool topmost)
+      : function_entry_event (dst_point),
+       m_pd (pd),
+       m_topmost (topmost)
+      {
+      }
+
+      label_text
+      get_desc (bool can_colorize) const final override
+      {
+       if (m_topmost)
+         {
+           if (m_pd.m_prev_entry_event
+               && m_pd.m_prev_entry_event->get_id_ptr ()->known_p ())
+             return make_label_text
+               (can_colorize,
+                "recursive entry to %qE; previously entered at %@",
+                m_effective_fndecl,
+                m_pd.m_prev_entry_event->get_id_ptr ());
+           else
+             return make_label_text (can_colorize, "recursive entry to %qE",
+                                     m_effective_fndecl);
+         }
+       else
+         return make_label_text (can_colorize, "initial entry to %qE",
+                                 m_effective_fndecl);
+      }
+
+    private:
+      const infinite_recursion_diagnostic &m_pd;
+      bool m_topmost;
+    };
+    const exploded_node *dst_node = eedge.m_dest;
+    const program_point &dst_point = dst_node->get_point ();
+    if (eedge.m_dest == m_prev_entry_enode)
+      {
+       gcc_assert (m_prev_entry_event == NULL);
+       std::unique_ptr<checker_event> prev_entry_event
+         = make_unique <recursive_function_entry_event> (dst_point,
+                                                         *this, false);
+       m_prev_entry_event = prev_entry_event.get ();
+       emission_path->add_event (std::move (prev_entry_event));
+      }
+    else if (eedge.m_dest == m_new_entry_enode)
+      emission_path->add_event
+       (make_unique<recursive_function_entry_event> (dst_point, *this, true));
+    else
+      pending_diagnostic::add_function_entry_event (eedge, emission_path);
+  }
+
+  /* Customize the location where the warning_event appears, putting
+     it at the topmost entrypoint to the function.  */
+  void add_final_event (const state_machine *,
+                       const exploded_node *,
+                       const gimple *,
+                       tree,
+                       state_machine::state_t,
+                       checker_path *emission_path) final override
+  {
+    gcc_assert (m_new_entry_enode);
+    emission_path->add_event
+      (make_unique<warning_event>
+       (m_new_entry_enode->get_supernode ()->get_start_location (),
+       m_callee_fndecl,
+       m_new_entry_enode->get_stack_depth (),
+       NULL, NULL, NULL));
+  }
+
+private:
+  const exploded_node *m_prev_entry_enode;
+  const exploded_node *m_new_entry_enode;
+  tree m_callee_fndecl;
+  const checker_event *m_prev_entry_event;
+};
+
+/* Return true iff ENODE is the PK_BEFORE_SUPERNODE at a function
+   entrypoint.  */
+
+static bool
+is_entrypoint_p (exploded_node *enode)
+{
+  /* Look for an entrypoint to a function...  */
+  const supernode *snode = enode->get_supernode ();
+  if (!snode)
+    return false;
+  if (!snode->entry_p ())
+    return false;;
+  const program_point &point = enode->get_point ();
+  if (point.get_kind () != PK_BEFORE_SUPERNODE)
+    return false;
+  return true;
+}
+
+/* Walk backwards through the eg, looking for the first
+   enode we find that's also the entrypoint of the same function.  */
+
+exploded_node *
+exploded_graph::find_previous_entry_to (function *top_of_stack_fun,
+                                       exploded_node *enode) const
+{
+  auto_vec<exploded_node *> worklist;
+  hash_set<exploded_node *> visited;
+
+  visited.add (enode);
+  for (auto in_edge : enode->m_preds)
+    worklist.safe_push (in_edge->m_src);
+
+  while (worklist.length () > 0)
+    {
+      exploded_node *iter = worklist.pop ();
+
+      if (is_entrypoint_p (iter)
+         && iter->get_function () == top_of_stack_fun)
+       return iter;
+
+      if (visited.contains (iter))
+       continue;
+      visited.add (iter);
+      for (auto in_edge : iter->m_preds)
+       worklist.safe_push (in_edge->m_src);
+    }
+
+  /* Not found.  */
+  return NULL;
+}
+
+/* Given BASE_REG within ENCLOSING_FRAME (such as a function parameter),
+   remap it to the equivalent region within EQUIV_PREV_FRAME.
+
+   For example, given param "n" within frame "foo@3", and equiv prev frame
+   "foo@1", remap it to param "n" within frame "foo@1".  */
+
+static const region *
+remap_enclosing_frame (const region *base_reg,
+                      const frame_region *enclosing_frame,
+                      const frame_region *equiv_prev_frame,
+                      region_model_manager *mgr)
+{
+  gcc_assert (base_reg->get_parent_region () == enclosing_frame);
+  switch (base_reg->get_kind ())
+    {
+    default:
+      /* We should only encounter params and varargs at the topmost
+        entrypoint.  */
+      gcc_unreachable ();
+
+    case RK_VAR_ARG:
+      {
+       const var_arg_region *var_arg_reg = (const var_arg_region *)base_reg;
+       return mgr->get_var_arg_region (equiv_prev_frame,
+                                       var_arg_reg->get_index ());
+      }
+    case RK_DECL:
+      {
+       const decl_region *decl_reg = (const decl_region *)base_reg;
+       return equiv_prev_frame->get_region_for_local (mgr,
+                                                      decl_reg->get_decl (),
+                                                      NULL);
+      }
+    }
+}
+
+/* Compare the state of memory at NEW_ENTRY_ENODE and PREV_ENTRY_ENODE,
+   both of which are entrypoints to the same function, where recursion has
+   occurred.
+
+   Return true if the state of NEW_ENTRY_ENODE is sufficiently different
+   from PREV_ENTRY_ENODE to suggests that some variant is being modified,
+   and thus the recursion isn't infinite.
+
+   Return false if the states are effectively the same, suggesting that
+   the recursion is infinite.
+
+   For example, consider mutually recursive functions "foo" and "bar".
+   At the entrypoint to a "foo" frame where we've detected recursion,
+   we might have three frames on the stack: the new 'foo'@3, an inner
+   'bar'@2, and the innermost 'foo'@1.
+
+     (gdb) call enode->dump(m_ext_state)
+     EN: 16
+     callstring: [(SN: 9 -> SN: 3 in foo), (SN: 5 -> SN: 8 in bar)]
+     before SN: 0 (NULL from-edge)
+
+     rmodel:
+     stack depth: 3
+       frame (index 2): frame: â€˜foo’@3
+       frame (index 1): frame: â€˜bar’@2
+       frame (index 0): frame: â€˜foo’@1
+     clusters within root region
+       cluster for: (*INIT_VAL(f_4(D)))
+     clusters within frame: â€˜bar’@2
+       cluster for: b_2(D): INIT_VAL(f_4(D))
+     clusters within frame: â€˜foo’@3
+       cluster for: f_4(D): INIT_VAL(f_4(D))
+     m_called_unknown_fn: FALSE
+
+   whereas for the previous entry node we'd have just the innermost
+   'foo'@1
+
+     (gdb) call prev_entry_enode->dump(m_ext_state)
+     EN: 1
+     callstring: []
+     before SN: 0 (NULL from-edge)
+
+     rmodel:
+     stack depth: 1
+       frame (index 0): frame: â€˜foo’@1
+     clusters within root region
+       cluster for: (*INIT_VAL(f_4(D)))
+     m_called_unknown_fn: FALSE
+
+   We want to abstract away frames 1 and 2 in the new entry enode,
+   and compare its frame 3 with the frame 1 in the previous entry
+   enode, and determine if enough state changes between them to
+   rule out infinite recursion.  */
+
+static bool
+sufficiently_different_p (exploded_node *new_entry_enode,
+                         exploded_node *prev_entry_enode,
+                         logger *logger)
+{
+  LOG_SCOPE (logger);
+  gcc_assert (new_entry_enode);
+  gcc_assert (prev_entry_enode);
+  gcc_assert (is_entrypoint_p (new_entry_enode));
+  gcc_assert (is_entrypoint_p (prev_entry_enode));
+
+  const int new_stack_depth = new_entry_enode->get_stack_depth ();
+
+  /* Compare the stores of the two enodes.  */
+  const region_model &new_model
+    = *new_entry_enode->get_state ().m_region_model;
+  const region_model &prev_model
+    = *prev_entry_enode->get_state ().m_region_model;
+  const store &new_store = *new_model.get_store ();
+
+  for (auto kv : new_store)
+    {
+      const region *base_reg = kv.first;
+
+      /* Get the value within the new frame.  */
+      const svalue *new_sval
+       = new_model.get_store_value (base_reg, NULL);
+
+      /* If the value is UNKNOWN (e.g. due to hitting complexity limits)
+        assume that it differs from the previous value.  */
+      if (new_sval->get_kind () == SK_UNKNOWN)
+       return true;
+
+      /* Get the equivalent value within the old enode.  */
+      const svalue *prev_sval;
+
+      if (const frame_region *enclosing_frame
+           = base_reg->maybe_get_frame_region ())
+       {
+         /* We have a binding within a frame in the new entry enode.  */
+
+         /* Ignore bindings within frames below the new entry node.  */
+         if (enclosing_frame->get_stack_depth () < new_stack_depth)
+           continue;
+
+         /* We have a binding within the frame of the new entry node,
+            presumably a parameter.  */
+
+         /* Get the value within the equivalent frame of
+            the old entrypoint; typically will be the initial_svalue
+            of the parameter.  */
+         const frame_region *equiv_prev_frame
+           = prev_model.get_current_frame ();
+         const region *equiv_prev_base_reg
+           = remap_enclosing_frame (base_reg,
+                                    enclosing_frame,
+                                    equiv_prev_frame,
+                                    new_model.get_manager ());
+         prev_sval = prev_model.get_store_value (equiv_prev_base_reg, NULL);
+       }
+      else
+       prev_sval = prev_model.get_store_value (base_reg, NULL);
+
+      /* If the prev_sval is UNKNOWN (e.g. due to hitting complexity limits)
+        assume that it will differ from any new value.  */
+      if (prev_sval->get_kind () == SK_UNKNOWN)
+       return true;
+
+      if (new_sval != prev_sval)
+       return true;
+    }
+
+  /* No significant differences found.  */
+  return false;
+}
+
+/* Implementation of -Wanalyzer-infinite-recursion.
+
+   Called when adding ENODE to the graph, after adding its first in-edge.
+
+   For function entrypoints, see if recursion has occurred, and, if so,
+   check if the state of memory changed between the recursion levels,
+   which would suggest some kind of decreasing variant that leads to
+   termination.
+
+   For recursive calls where the state of memory is effectively unchanged
+   between recursion levels, warn with -Wanalyzer-infinite-recursion.  */
+
+void
+exploded_graph::detect_infinite_recursion (exploded_node *enode)
+{
+  if (!is_entrypoint_p (enode))
+    return;
+  function *top_of_stack_fun = enode->get_function ();
+  gcc_assert (top_of_stack_fun);
+
+  /* ....where a call to that function is already in the call string.  */
+  const call_string &call_string = enode->get_point ().get_call_string ();
+
+  if (call_string.count_occurrences_of_function (top_of_stack_fun) < 2)
+    return;
+
+  tree fndecl = top_of_stack_fun->decl;
+
+  log_scope s (get_logger (),
+              "checking for infinite recursion",
+              "considering recursion at EN: %i entering %qE",
+              enode->m_index, fndecl);
+
+  /* Find enode that's the entrypoint for the previous frame for fndecl
+     in the recursion.  */
+  exploded_node *prev_entry_enode
+    = find_previous_entry_to (top_of_stack_fun, enode);
+  gcc_assert (prev_entry_enode);
+  if (get_logger ())
+    get_logger ()->log ("previous entrypoint to %qE is EN: %i",
+                       fndecl, prev_entry_enode->m_index);
+
+  /* Look for changes to the state of memory between the recursion levels.  */
+  if (sufficiently_different_p (enode, prev_entry_enode, get_logger ()))
+    return;
+
+  /* Otherwise, the state of memory is effectively the same between the two
+     recursion levels; warn.  */
+
+  const supernode *caller_snode = call_string.get_top_of_stack ().m_caller;
+  const supernode *snode = enode->get_supernode ();
+  gcc_assert (caller_snode->m_returning_call);
+  get_diagnostic_manager ().add_diagnostic
+    (enode, snode, caller_snode->m_returning_call, NULL,
+     make_unique<infinite_recursion_diagnostic> (prev_entry_enode,
+                                                enode,
+                                                fndecl));
+}
index fdbe6153f2c73a7564308b34e0ddd63c99635d39..9216a22e64def8547f7fc4c4a421457b7f8d3408 100644 (file)
@@ -167,6 +167,18 @@ pending_diagnostic::fixup_location (location_t loc) const
   return loc;
 }
 
+/* Base implementation of pending_diagnostic::add_function_entry_event.
+   Add a function_entry_event to EMISSION_PATH.  */
+
+void
+pending_diagnostic::add_function_entry_event (const exploded_edge &eedge,
+                                             checker_path *emission_path)
+{
+  const exploded_node *dst_node = eedge.m_dest;
+  const program_point &dst_point = dst_node->get_point ();
+  emission_path->add_event (make_unique<function_entry_event> (dst_point));
+}
+
 /* Base implementation of pending_diagnostic::add_call_event.
    Add a call_event to EMISSION_PATH.  */
 
@@ -187,6 +199,24 @@ pending_diagnostic::add_call_event (const exploded_edge &eedge,
                              src_stack_depth));
 }
 
+/* Base implementation of pending_diagnostic::add_final_event.
+   Add a warning_event to the end of EMISSION_PATH.  */
+
+void
+pending_diagnostic::add_final_event (const state_machine *sm,
+                                    const exploded_node *enode,
+                                    const gimple *stmt,
+                                    tree var, state_machine::state_t state,
+                                    checker_path *emission_path)
+{
+  emission_path->add_event
+    (make_unique<warning_event> (get_stmt_location (stmt,
+                                                   enode->get_function ()),
+                                enode->get_function ()->decl,
+                                enode->get_stack_depth (),
+                                sm, var, state));
+}
+
 } // namespace ana
 
 #endif /* #if ENABLE_ANALYZER */
index 6ca8ab9f4aad73b2759bfc5b654a113bfd716fe5..0e91e71a2089e4ba06f98cb0c78f76e525bf410f 100644 (file)
@@ -309,6 +309,14 @@ class pending_diagnostic
 
   /* End of precision-of-wording vfuncs.  */
 
+  /* Vfunc for adding a function_entry_event to a checker_path, so that e.g.
+     the infinite recursion diagnostic can add a custom event subclass
+     that annotates recursively entering a function.  */
+
+  virtual void
+  add_function_entry_event (const exploded_edge &eedge,
+                           checker_path *emission_path);
+
   /* Vfunc for extending/overriding creation of the events for an
      exploded_edge that corresponds to a superedge, allowing for custom
      events to be created that are pertinent to a particular
@@ -330,6 +338,16 @@ class pending_diagnostic
   virtual void add_call_event (const exploded_edge &,
                               checker_path *);
 
+  /* Vfunc for adding the final warning_event to a checker_path, so that e.g.
+     the infinite recursion diagnostic can have its diagnostic appear at
+     the callsite, but the final event in the path be at the entrypoint
+     of the called function.  */
+  virtual void add_final_event (const state_machine *sm,
+                               const exploded_node *enode,
+                               const gimple *stmt,
+                               tree var, state_machine::state_t state,
+                               checker_path *emission_path);
+
   /* Vfunc for determining that this pending_diagnostic supercedes OTHER,
      and that OTHER should therefore not be emitted.
      They have already been tested for being at the same stmt.  */
index 09bf049036f1bad130dcd3932a6711737457caaf..32a626c16a9aaf410628f4889bcb382ddb84ea4e 100644 (file)
@@ -32,6 +32,7 @@ Options That Control Static Analysis
   :option:`-Wanalyzer-file-leak` |gol|
   :option:`-Wanalyzer-free-of-non-heap` |gol|
   :option:`-Wanalyzer-imprecise-fp-arithmetic` |gol|
+  :option:`-Wanalyzer-infinite-recursion` |gol|
   :option:`-Wanalyzer-jump-through-null` |gol|
   :option:`-Wanalyzer-malloc-leak` |gol|
   :option:`-Wanalyzer-mismatching-deallocation` |gol|
@@ -308,6 +309,33 @@ Options That Control Static Analysis
 
   Default setting; overrides :option:`-Wno-analyzer-imprecise-fp-arithmetic`.
 
+.. option:: -Wno-analyzer-infinite-recursion
+
+  This warning requires :option:`-fanalyzer`, which enables it; use
+  :option:`-Wno-analyzer-infinite-recursion` to disable it.
+
+  This diagnostics warns for paths through the code which appear to
+  lead to infinite recursion.
+
+  Specifically, when the analyzer "sees" a recursive call, it will compare
+  the state of memory at the entry to the new frame with that at the entry
+  to the previous frame of that function on the stack.  The warning is
+  issued if nothing in memory appears to be changing; any changes observed
+  to parameters or globals are assumed to lead to termination of the
+  recursion and thus suppress the warning.
+
+  This diagnostic is likely to miss cases of infinite recursion that
+  are convered to iteration by the optimizer before the analyzer "sees"
+  them.  Hence optimization should be disabled when attempting to trigger
+  this diagnostic.
+
+  Compare with :option:`-Winfinite-recursion`, which provides a similar
+  diagnostic, but is implemented in a different way.
+
+.. option:: -Wanalyzer-infinite-recursion
+
+  Default setting; overrides :option:`-Wno-analyzer-infinite-recursion`.
+
 .. option:: -Wno-analyzer-jump-through-null
 
   This warning requires :option:`-fanalyzer`, which enables it; use
index a4a288859c6992a5a99fe4b4b09bae211ffd6795..b57f4784780530157ca874fc438060a91303d704 100644 (file)
@@ -804,6 +804,10 @@ warnings, in some cases it may also cause false positives.
   recursion in calls between two or more functions.
   :option:`-Winfinite-recursion` is included in :option:`-Wall`.
 
+  Compare with :option:`-Wanalyzer-infinite-recursion` which provides a
+  similar diagnostic, but is implemented in a different way (as part of
+  :option:`-fanalyzer`).
+
 .. option:: -Wno-infinite-recursion
 
   Default setting; overrides :option:`-Winfinite-recursion`.
diff --git a/gcc/testsuite/g++.dg/analyzer/infinite-recursion-1.C b/gcc/testsuite/g++.dg/analyzer/infinite-recursion-1.C
new file mode 100644 (file)
index 0000000..33f3b33
--- /dev/null
@@ -0,0 +1,84 @@
+class widget
+{
+public:
+  virtual void draw ()
+  {
+    /* no-op */
+  }
+};
+
+class foo_widget : public widget
+{
+public:
+  void draw ();
+};
+
+void foo_widget::draw ()
+{
+  // Bogus attempt to chain up to base class leading to infinite recursion:
+  foo_widget::draw (); /* { dg-warning "infinite recursion" } */
+
+  // [...snip...]
+}
+
+/* Infinite recursion due to a buggy "operator int".  */
+
+class boxed_int
+{
+  int m_val;
+public:
+  operator int ();
+};
+
+boxed_int::operator int ()
+{
+  return *this; /* { dg-warning "infinite recursion" } */
+}
+
+template <typename T>
+class buggy_getter
+{
+public:
+  T get_value () const
+  {
+    return get_value (); /* { dg-warning "infinite recursion" } */
+  }
+};
+
+int test_buggy_getter (buggy_getter<int> g)
+{
+  return g.get_value ();
+}
+
+/* Copy of g++.dg/warn/Winfinite-recursion.C  */
+
+template <typename D>
+struct C
+{
+  void foo ()
+  {
+    static_cast<D *>(this)->foo (); /* { dg-warning "-Wanalyzer-infinite-recursion" } */
+  }
+};
+
+struct D : C<D>
+{
+  // this is missing:
+  // void foo() {}
+};
+
+void f (D *d)
+{
+  d->foo ();
+}
+
+
+struct E : C<D>
+{
+  void foo() {}
+};
+
+void g (E *e)
+{
+  e->foo ();
+}
diff --git a/gcc/testsuite/g++.dg/analyzer/infinite-recursion-2.C b/gcc/testsuite/g++.dg/analyzer/infinite-recursion-2.C
new file mode 100644 (file)
index 0000000..c582fbf
--- /dev/null
@@ -0,0 +1,74 @@
+/* Copy of g++.dg/warn/Winfinite-recursion-2.C  */
+
+/* { dg-do compile { target c++11 } }  */
+
+namespace std
+{
+class type_info {
+public:
+  void k() const;
+};
+
+} // namespace std
+
+using std::type_info;
+
+template <int a> struct f { static constexpr int c = a; };
+struct h {
+  typedef int e;
+};
+
+template <unsigned long, typename...> struct m;
+template <unsigned long ab, typename i, typename j, typename... ac>
+struct m<ab, i, j, ac...> : m<ab + 1, i, ac...> {};
+template <unsigned long ab, typename j, typename... ac>
+struct m<ab, j, j, ac...> : f<ab> {};
+template <unsigned long, typename...> struct n;
+template <unsigned long ab, typename j, typename... ac>
+struct n<ab, j, ac...> : n<ab - 1, ac...> {};
+template <typename j, typename... ac> struct n<0, j, ac...> : h {};
+template <typename... l> class F {
+  template <typename i> struct I : m<0, i, l...> {};
+  template <int ab> struct s : n<ab, l...> {};
+  static const type_info *const b[];
+  struct G {
+    template <typename ag>
+    operator ag() const
+    {
+      return *this; /* { dg-warning "-Wanalyzer-infinite-recursion" } */
+    }
+  };
+  unsigned o;
+  G ah;
+
+public:
+  F();
+  long t() const { return o; }
+  const type_info &m_fn3() const { return *b[o]; }
+  template <int ab> typename s<ab>::e *m_fn4() const {
+    if (o != ab)
+      return nullptr;
+    return ah;
+  }
+  template <int ab> void m_fn5() const {
+    m_fn4<ab>();
+    const type_info &r = m_fn3();
+    r.k();
+  }
+  template <typename i> void u() const { m_fn5<I<i>::c>(); }
+};
+template <typename... l> const type_info *const F<l...>::b[] {&typeid(l)...};
+using am = unsigned char;
+class H {
+  enum bd : am { be = 2 };
+  using bf = F<int, int, H>;
+  bf ah;
+  template <typename bg> void v() const { ah.u<bg>(); }
+  void w() const;
+};
+void H::w() const {
+  bd d = bd(ah.t());
+  switch (d)
+  case be:
+    v<H>();
+}
diff --git a/gcc/testsuite/g++.dg/analyzer/infinite-recursion-3.C b/gcc/testsuite/g++.dg/analyzer/infinite-recursion-3.C
new file mode 100644 (file)
index 0000000..a749f62
--- /dev/null
@@ -0,0 +1,62 @@
+/* Adapted from g++.dg/warn/Winfinite-recursion-3.C  */
+
+typedef __SIZE_TYPE__ size_t;
+
+/* Might throw.  */
+void f ();
+
+void warn_f_call_r (int  n)
+{
+  if (n > 7)
+    f ();
+  warn_f_call_r (n - 1);
+}
+
+void warn_f_do_while_call_r (int n)
+{
+  f ();
+  do
+    {
+      f ();
+      warn_f_do_while_call_r (n - 1);
+    }
+  while (1);
+}
+
+
+struct X
+{
+  X (int);
+  ~X ();
+};
+
+int warn_class_with_ctor (int n)
+{
+  X x (n);
+  return n + warn_class_with_ctor (n - 1);
+}
+
+
+int nowarn_throw (int n)
+{
+  if (n > 7)
+    throw "argument too big";
+
+  return n + nowarn_throw (n - 1);
+}
+
+extern int* eipa[];
+
+void warn_call_new (int i)
+{
+  eipa[i] = new int;
+
+  warn_call_new (i - 1);
+}
+
+void* operator new[] (size_t n)
+{
+  char *p = new char[n + sizeof (n)];
+  *(size_t*)p = n;
+  return p + sizeof n;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-2.c b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-2.c
new file mode 100644 (file)
index 0000000..f0ab130
--- /dev/null
@@ -0,0 +1,109 @@
+void test_direct (void)
+{
+  test_direct (); /* { dg-warning "infinite recursion" } */
+}
+
+void test_guarded (int flag)
+{
+  if (flag)
+    test_guarded (flag); /* { dg-warning "infinite recursion" } */
+}
+
+void test_flipped_guard (int flag)
+{
+  if (flag)
+    test_guarded (!flag); /* { dg-bogus "infinite recursion" } */
+}
+
+void test_param_variant (int depth)
+{
+  if (depth > 0)
+    test_param_variant (depth - 1); /* { dg-bogus "infinite recursion" } */
+}
+
+void test_unguarded_param_variant (int depth)
+{
+  /* We fail to report this: we see that depth is being decremented,
+     but don't notice that every path through the function is
+     recursing.  */
+  test_unguarded_param_variant (depth - 1); /* { dg-warning "infinite recursion" "TODO" { xfail *-*-* } } */
+}
+
+int g;
+
+void test_global_variant ()
+{
+  if (g-- > 0)
+    test_global_variant (); /* { dg-bogus "infinite recursion" } */
+}
+
+/* This is a bounded recursion, as "n" is decremented before recursing... */
+
+int test_while_do_predecrement_param (int n)
+{
+  int x = 0;
+  while (n)
+    x += test_while_do_predecrement_param (--n); /* { dg-bogus "infinite recursion" } */
+  return x;
+}
+
+/* ...whereas this one is unbounded, as "n" is decremented *after* the
+   recursive call, and so is repeatedly called with the same value.  */
+
+int test_while_do_postdecrement_param (int n)
+{
+  int x = 0;
+  while (n)
+    x += test_while_do_postdecrement_param (n--); /* { dg-warning "infinite recursion" } */
+  return x;
+}
+/* This is a bounded recursion, as "n" is decremented before recursing... */
+
+int test_do_while_predecrement_param (int n)
+{
+  int x = 0;
+  do
+    x += test_do_while_predecrement_param (--n); /* { dg-bogus "infinite recursion" } */
+  while (--n);
+  return x;
+}
+
+/* ...whereas this one is unbounded, as "n" is decremented *after* the
+   recursive call, and so is repeatedly called with the same value.  */
+
+int test_do_while_postdecrement_param (int n)
+{
+  int x = 0;
+  do
+    x += test_do_while_postdecrement_param (n--); /* { dg-warning "infinite recursion" } */
+  while (--n);
+  return x;
+}
+
+/* Various cases of decrementing "n" as the recursion proceeds where
+   not every path recurses, but we're not actually checking "n", so
+   if "flag" is true it's an infinite recursion.  */
+
+void test_partially_guarded_postdecrement (int flag, int n)
+{
+  /* We catch this; the "n--" means we recurse with the
+     same value for the 2nd param.  */
+  if (flag) /* { dg-message "when 'flag != 0'" } */
+    test_partially_guarded_postdecrement (flag, n--); /* { dg-warning "infinite recursion" } */
+}
+
+void test_partially_guarded_predecrement (int flag, int n)
+{
+  /* We fail to report this; we see that "n" is changing,
+     though it isn't relevant to whether we recurse.  */
+  if (flag)
+    test_partially_guarded_predecrement (flag, --n); /* { dg-warning "infinite recursion" "TODO" { xfail *-*-* } } */
+}
+
+void test_partially_guarded_subtract (int flag, int n)
+{
+  /* We fail to report this; we see that "n" is changing,
+     though it isn't relevant to whether we recurse.  */
+  if (flag)
+    test_partially_guarded_subtract (flag, n - 1); /* { dg-warning "infinite recursion" "TODO" { xfail *-*-* } } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-3.c b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-3.c
new file mode 100644 (file)
index 0000000..68c4fa3
--- /dev/null
@@ -0,0 +1,18 @@
+/* { dg-additional-options "-fno-analyzer-call-summaries -Wno-analyzer-too-complex" } */
+
+struct node
+{
+  struct node *left;
+  struct node *right;
+  int val;
+};
+
+int sum (struct node *n)
+{
+  int result = 0;
+  if (n->left)
+    result += sum (n->left); /* { dg-bogus "infinite recursion" } */
+  if (n->right)
+    result += sum (n->right); /* { dg-bogus "infinite recursion" } */
+  return result;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-4-limited-buggy.c b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-4-limited-buggy.c
new file mode 100644 (file)
index 0000000..a71b902
--- /dev/null
@@ -0,0 +1,25 @@
+/* { dg-additional-options "-fno-analyzer-call-summaries -Wno-analyzer-too-complex" } */
+
+/* A two-deep mutual recursion, and failing to walk a list,
+   but with a depth limit, thus not an infinite recursion (assuming a
+   suitable depth limit).  */
+
+struct node
+{
+  struct node *child;
+};
+
+void foo (struct node *f, int depth);
+
+void bar (struct node *b, int depth)
+{
+  foo (b, depth); /* { dg-bogus "infinite recursion" } */
+}
+
+void foo (struct node *f, int depth)
+{
+  if (f->child && depth > 0)
+    /* Bug: should have recursed to f->child, not to f,
+       but we assume that the depth limit should save us.  */
+    bar (f, depth - 1); /* { dg-bogus "infinite recursion" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-4-limited.c b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-4-limited.c
new file mode 100644 (file)
index 0000000..55326d2
--- /dev/null
@@ -0,0 +1,22 @@
+/* { dg-additional-options "-fno-analyzer-call-summaries -Wno-analyzer-too-complex" } */
+
+/* A two-deep mutual recursion, walking a singly-linked list,
+   with a depth limit.  */
+
+struct node
+{
+  struct node *child;
+};
+
+void foo (struct node *f, int depth);
+
+void bar (struct node *b, int depth)
+{
+  foo (b, depth); /* { dg-bogus "infinite recursion" } */
+}
+
+void foo (struct node *f, int depth)
+{
+  if (f->child && depth > 0)
+    bar (f->child, depth - 1); /* { dg-bogus "infinite recursion" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-4-unlimited-buggy.c b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-4-unlimited-buggy.c
new file mode 100644 (file)
index 0000000..7ed1a2b
--- /dev/null
@@ -0,0 +1,23 @@
+/* { dg-additional-options "-fno-analyzer-call-summaries -Wno-analyzer-too-complex" } */
+
+/* A two-deep mutual recursion, with no limit, and
+   failing to walk the list, thus leading to an infinite recursion.  */
+
+struct node
+{
+  struct node *child;
+};
+
+void foo (struct node *f);
+
+void bar (struct node *b)
+{
+  foo (b); /* { dg-warning "infinite recursion" } */
+}
+
+void foo (struct node *f)
+{
+  if (f->child)
+    /* Bug: should have recursed to f->child, not to f.  */
+    bar (f); /* { dg-warning "infinite recursion" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-4-unlimited.c b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-4-unlimited.c
new file mode 100644 (file)
index 0000000..bdb54a3
--- /dev/null
@@ -0,0 +1,22 @@
+/* { dg-additional-options "-fno-analyzer-call-summaries -Wno-analyzer-too-complex" } */
+
+/* A two-deep mutual recursion, walking a linked list (and thus presumably
+   terminating), with no explicit depth limit.  */
+
+struct node
+{
+  struct node *child;
+};
+
+void foo (struct node *f);
+
+void bar (struct node *b)
+{
+  foo (b); /* { dg-bogus "infinite recursion" } */
+}
+
+void foo (struct node *f)
+{
+  if (f->child)
+    bar (f->child); /* { dg-bogus "infinite recursion" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-5.c b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-5.c
new file mode 100644 (file)
index 0000000..bf20639
--- /dev/null
@@ -0,0 +1,221 @@
+/* Adapted from gcc.dg/Winfinite-recursion.c.  */
+
+#define NORETURN __attribute__ ((noreturn))
+
+typedef __SIZE_TYPE__ size_t;
+
+extern void abort (void);
+extern void exit (int);
+
+extern int ei;
+int (*pfi_v)(void);
+
+
+/* Make sure the warning doesn't assume every call has a DECL.  */
+
+int nowarn_pfi_v (void)
+{
+  return pfi_v ();
+}
+
+
+int warn_fi_v (void)
+{
+  return warn_fi_v ();               // { dg-warning "-Wanalyzer-infinite-recursion" }
+}
+
+/* Verify #pragma suppression works.  */
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wanalyzer-infinite-recursion"
+
+int suppress_warn_fi_v (void)
+{
+  return suppress_warn_fi_v ();
+}
+
+#pragma GCC diagnostic pop
+
+
+int nowarn_fi_v (void)
+{
+  if (ei++ == 0)
+    return nowarn_fi_v ();
+  return 0;
+}
+
+/* -Winfinite-recursion warns for this, but
+   -Wanalyzer-infinite-recursion doesn't.  */
+
+int warn_if_i (int i)
+{
+  if (i > 0)
+    return warn_if_i (--i);
+  else if (i < 0)
+    return warn_if_i (-i);
+  else
+    return warn_if_i (7);
+}
+
+
+int nowarn_if_i (int i)
+{
+  if (i > 0)
+    return nowarn_if_i (--i);
+  else if (i < 0)
+    return nowarn_if_i (-i);
+  else
+    return -1;
+}
+
+int nowarn_switch (int i, int a[])
+{
+  switch (i)
+    {
+    case 0: return nowarn_switch (a[3], a + 1);
+    case 1: return nowarn_switch (a[5], a + 2);
+    case 2: return nowarn_switch (a[7], a + 3);
+    case 3: return nowarn_switch (a[9], a + 4);
+    }
+  return 77;
+}
+
+/* -Winfinite-recursion warns for this, but
+   -Wanalyzer-infinite-recursion doesn't.  */
+
+int warn_switch (int i, int a[])
+{
+  switch (i)
+    {
+    case 0: return warn_switch (a[3], a + 1);
+    case 1: return warn_switch (a[5], a + 2);
+    case 2: return warn_switch (a[7], a + 3);
+    case 3: return warn_switch (a[9], a + 4);
+    default: return warn_switch (a[1], a + 5);
+    }
+}
+
+NORETURN void fnoreturn (void);
+
+/* Verify there's no warning for a function that doesn't return.  */
+int nowarn_call_noret (void)
+{
+  fnoreturn ();
+}
+
+int warn_call_noret_r (void)
+{
+  warn_call_noret_r ();             // { dg-warning "-Wanalyzer-infinite-recursion" }
+  fnoreturn ();
+}
+
+/* -Winfinite-recursion warns for this, but
+   -Wanalyzer-infinite-recursion doesn't.  */
+
+int
+warn_noret_call_abort_r (char *s, int n)
+{
+  if (!s)
+    abort ();
+
+  if (n > 7)
+    abort ();
+
+  return n + warn_noret_call_abort_r (s, n - 1);
+}
+
+NORETURN void nowarn_noret_call_abort_r (int n)
+{
+  if (n > 7)
+    abort ();
+
+  nowarn_noret_call_abort_r (n - 1);
+}
+
+/* -Winfinite-recursion warns for this, but
+   -Wanalyzer-infinite-recursion doesn't.  */
+
+int warn_call_abort_r (int n)
+{
+  n += warn_call_abort_r (n - 1);
+  if (n > 7)   // unreachable
+    abort ();
+  return n;
+}
+
+
+/* -Winfinite-recursion warns for this, but
+   -Wanalyzer-infinite-recursion doesn't.  */
+
+int warn_call_exit_r (int n)
+{
+  n += warn_call_exit_r (n - 1);
+  if (n > 7)
+    exit (0);
+  return n;
+}
+
+struct __jmp_buf_tag { };
+typedef struct __jmp_buf_tag jmp_buf[1];
+
+extern jmp_buf jmpbuf;
+
+/* A call to longjmp() breaks infinite recursion.  Verify it suppresses
+   the warning.  */
+
+int nowarn_call_longjmp_r (int n)
+{
+  if (n > 7)
+    __builtin_longjmp (jmpbuf, 1);
+  return n + nowarn_call_longjmp_r (n - 1);
+}
+
+/* -Winfinite-recursion warns for this, but
+   -Wanalyzer-infinite-recursion doesn't.  */
+
+int warn_call_longjmp_r (int n)
+{
+  n += warn_call_longjmp_r (n - 1);
+  if (n > 7)
+    __builtin_longjmp (jmpbuf, 1);
+  return n;
+}
+
+
+struct __sigjmp_buf_tag { };
+typedef struct __sigjmp_buf_tag sigjmp_buf[1];
+
+extern sigjmp_buf sigjmpbuf;
+
+/* GCC has no __builtin_siglongjmp().  */
+extern void siglongjmp (sigjmp_buf, int);
+
+/* A call to longjmp() breaks infinite recursion.  Verify it suppresses
+   the warning.  */
+
+int nowarn_call_siglongjmp_r (int n)
+{
+  if (n > 7)
+    siglongjmp (sigjmpbuf, 1);
+  return n + nowarn_call_siglongjmp_r (n - 1);
+}
+
+/* -Winfinite-recursion doesn't warn for this unbounded recursion, but
+   -Wanalyzer-infinite-recursion does.  */
+
+int nowarn_while_do_call_r (int n)
+{
+  int z = 0;
+  while (n)
+    z += nowarn_while_do_call_r (n--); // { dg-warning "-Wanalyzer-infinite-recursion" }
+  return z;
+}
+
+int warn_do_while_call_r (int n)
+{
+  int z = 0;
+  do
+    z += warn_do_while_call_r (n); // { dg-warning "-Wanalyzer-infinite-recursion" }
+  while (--n);
+  return z;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-alloca.c b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-alloca.c
new file mode 100644 (file)
index 0000000..8c50631
--- /dev/null
@@ -0,0 +1,27 @@
+typedef __SIZE_TYPE__ size_t;
+
+int test_alloca_1 (void)
+{
+  void *buf = __builtin_alloca (1024);
+  return test_alloca_1 (); /* { dg-warning "-Wanalyzer-infinite-recursion" } */
+}
+
+int test_alloca_2 (size_t n)
+{
+  void *buf = __builtin_alloca (n);
+  return test_alloca_2 (n); /* { dg-warning "-Wanalyzer-infinite-recursion" } */
+}
+
+int test_alloca_3 (size_t n)
+{
+  void *buf = __builtin_alloca (n);
+  return test_alloca_2 (n - 1);
+}
+
+int test_alloca_4 (size_t n)
+{
+  void *buf = __builtin_alloca (n);
+  if (n > 0)
+    return test_alloca_2 (n - 1);
+  return 42;
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-inlining.c b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-inlining.c
new file mode 100644 (file)
index 0000000..c885c92
--- /dev/null
@@ -0,0 +1,116 @@
+/* A copy of infinite-recursion-2.c, to see what inlining does to the IR
+   when we see it.
+
+   Many cases get converted by the optimizer into iteration, and
+   into infinite loops, sometimes trivial ones.
+
+   Right now this is a documented limitation of the warning, but perhaps
+   could be readdressed by moving the analyzer earlier.   */
+
+/* { dg-additional-options "-O3" } */
+
+void test_direct (void)
+{
+  test_direct (); /* Ideally would warn here, but it becomes an infinite loop.  */
+}
+
+void test_guarded (int flag)
+{
+  if (flag)
+    test_guarded (flag);  /* Ideally would warn here, but it becomes an infinite loop.  */
+}
+
+void test_flipped_guard (int flag)
+{
+  if (flag)
+    test_guarded (!flag);
+}
+
+void test_param_variant (int depth)
+{
+  if (depth > 0)
+    test_param_variant (depth - 1);
+}
+
+void test_unguarded_param_variant (int depth)
+{
+  test_unguarded_param_variant (depth - 1); /* Ideally would warn here, but it becomes an infinite loop.  */
+}
+
+int g;
+
+void test_global_variant ()
+{
+  if (g-- > 0)
+    test_global_variant ();
+}
+
+/* This is a bounded recursion, as "n" is decremented before recursing... */
+
+int test_while_do_predecrement_param (int n)
+{
+  int x = 0;
+  while (n)
+    x += test_while_do_predecrement_param (--n);
+  return x;
+}
+
+/* ...whereas this one is unbounded, as "n" is decremented *after* the
+   recursive call, and so is repeatedly called with the same value.  */
+
+int test_while_do_postdecrement_param (int n)
+{
+  int x = 0;
+  while (n)
+    x += test_while_do_postdecrement_param (n--); /* { dg-warning "infinite recursion" } */
+  return x;
+}
+/* This is a bounded recursion, as "n" is decremented before recursing... */
+
+int test_do_while_predecrement_param (int n)
+{
+  int x = 0;
+  do
+    x += test_do_while_predecrement_param (--n);
+  while (--n);
+  return x;
+}
+
+/* ...whereas this one is unbounded, as "n" is decremented *after* the
+   recursive call, and so is repeatedly called with the same value.  */
+
+int test_do_while_postdecrement_param (int n)
+{
+  int x = 0;
+  do
+    x += test_do_while_postdecrement_param (n--); /* { dg-warning "infinite recursion" } */
+  while (--n);
+  return x;
+}
+
+/* Various cases of decrementing "n" as the recursion proceeds where
+   not every path recurses, but we're not actually checking "n", so
+   if "flag" is true it's an infinite recursion.  */
+
+void test_partially_guarded_postdecrement (int flag, int n)
+{
+  /* Ideally we'd catch this, but it becomes an infinite loop.  */
+  if (flag)
+    test_partially_guarded_postdecrement (flag, n--);
+}
+
+void test_partially_guarded_predecrement (int flag, int n)
+{
+  /* We fail to report this; we see that "n" is changing,
+     though it isn't relevant to whether we recurse.  */
+  if (flag)
+    test_partially_guarded_predecrement (flag, --n); /* { dg-warning "infinite recursion" "TODO" { xfail *-*-* } } */
+}
+
+void test_partially_guarded_subtract (int flag, int n)
+{
+  /* We fail to report this; we see that "n" is changing,
+     though it isn't relevant to whether we recurse.  */
+  if (flag)
+    test_partially_guarded_subtract (flag, n - 1); /* { dg-warning "infinite recursion" "TODO" { xfail *-*-* } } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-multiline-1.c b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-multiline-1.c
new file mode 100644 (file)
index 0000000..e236dd4
--- /dev/null
@@ -0,0 +1,41 @@
+/* Integration test of how the execution path looks for
+   -Wanalyzer-infinite-recursion.  */
+
+/* { dg-additional-options "-fdiagnostics-show-path-depths" } */
+/* { dg-additional-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
+
+void foo (int flag)
+{
+  if (flag)
+    foo (flag); /* { dg-warning "infinite recursion" } */
+}
+
+/* { dg-begin-multiline-output "" }
+     foo (flag);
+     ^~~~~~~~~~
+  'foo': events 1-4 (depth 1)
+    |
+    | void foo (int flag)
+    |      ^~~
+    |      |
+    |      (1) initial entry to 'foo'
+    |
+    |   if (flag)
+    |      ~
+    |      |
+    |      (2) following 'true' branch (when 'flag != 0')...
+    |     foo (flag);
+    |     ~~~~~~~~~~
+    |     |
+    |     (3) ...to here
+    |     (4) calling 'foo' from 'foo'
+    |
+    +--> 'foo': events 5-6 (depth 2)
+           |
+           | void foo (int flag)
+           |      ^~~
+           |      |
+           |      (5) recursive entry to 'foo'; previously entered at (1)
+           |      (6) apparently infinite recursion
+           |
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-multiline-2.c b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-multiline-2.c
new file mode 100644 (file)
index 0000000..2c69dd5
--- /dev/null
@@ -0,0 +1,93 @@
+/* Integration test of how the execution path looks for
+   -Wanalyzer-infinite-recursion.  */
+
+/* { dg-additional-options "-fdiagnostics-show-path-depths" } */
+/* { dg-additional-options "-fdiagnostics-path-format=inline-events -fdiagnostics-show-caret" } */
+
+void mutual_2 (void);
+
+void mutual_1 (void)
+{
+  mutual_2 (); /* { dg-warning "infinite recursion" } */
+}
+
+void mutual_2 (void)
+{
+  mutual_1 (); /* { dg-warning "infinite recursion" } */
+}
+
+
+/* { dg-begin-multiline-output "" }
+   mutual_2 ();
+   ^~~~~~~~~~~
+  'mutual_2': events 1-2 (depth 1)
+    |
+    | void mutual_2 (void)
+    |      ^~~~~~~~
+    |      |
+    |      (1) initial entry to 'mutual_2'
+    |
+    |   mutual_1 ();
+    |   ~~~~~~~~~~~
+    |   |
+    |   (2) calling 'mutual_1' from 'mutual_2'
+    |
+    +--> 'mutual_1': events 3-4 (depth 2)
+           |
+           | void mutual_1 (void)
+           |      ^~~~~~~~
+           |      |
+           |      (3) entry to 'mutual_1'
+           |
+           |   mutual_2 ();
+           |   ~~~~~~~~~~~
+           |   |
+           |   (4) calling 'mutual_2' from 'mutual_1'
+           |
+           +--> 'mutual_2': events 5-6 (depth 3)
+                  |
+                  | void mutual_2 (void)
+                  |      ^~~~~~~~
+                  |      |
+                  |      (5) recursive entry to 'mutual_2'; previously entered at (1)
+                  |      (6) apparently infinite chain of mutually-recursive function calls, consuming 2 stack frames per recursion
+                  |
+   { dg-end-multiline-output "" } */
+
+
+/* { dg-begin-multiline-output "" }
+   mutual_1 ();
+   ^~~~~~~~~~~
+  'mutual_1': events 1-2 (depth 1)
+    |
+    | void mutual_1 (void)
+    |      ^~~~~~~~
+    |      |
+    |      (1) initial entry to 'mutual_1'
+    |
+    |   mutual_2 ();
+    |   ~~~~~~~~~~~
+    |   |
+    |   (2) calling 'mutual_2' from 'mutual_1'
+    |
+    +--> 'mutual_2': events 3-4 (depth 2)
+           |
+           | void mutual_2 (void)
+           |      ^~~~~~~~
+           |      |
+           |      (3) entry to 'mutual_2'
+           |
+           |   mutual_1 ();
+           |   ~~~~~~~~~~~
+           |   |
+           |   (4) calling 'mutual_1' from 'mutual_2'
+           |
+           +--> 'mutual_1': events 5-6 (depth 3)
+                  |
+                  | void mutual_1 (void)
+                  |      ^~~~~~~~
+                  |      |
+                  |      (5) recursive entry to 'mutual_1'; previously entered at (1)
+                  |      (6) apparently infinite chain of mutually-recursive function calls, consuming 2 stack frames per recursion
+                  |
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-variadic.c b/gcc/testsuite/gcc.dg/analyzer/infinite-recursion-variadic.c
new file mode 100644 (file)
index 0000000..eafbeba
--- /dev/null
@@ -0,0 +1,34 @@
+int test_variadic_1 (int n, ...)
+{
+  __builtin_va_list args;
+  int total =0;
+  int i;
+
+  __builtin_va_start(args, n);
+
+  for (i = 0; i < n; i++)
+    total += __builtin_va_arg(args, int);
+
+  __builtin_va_end(args);
+
+  return total;  
+}
+
+int test_variadic_2 (int n, ...)
+{
+  return test_variadic_2 (n, 42); /* { dg-warning "-Wanalyzer-infinite-recursion" } */
+}
+
+int test_variadic_3 (int n, ...)
+{
+  if (n > 0) /* { dg-message "when 'n > 0'" } */
+    return test_variadic_3 (n, 42); /* { dg-warning "-Wanalyzer-infinite-recursion" } */
+  return 0;
+}
+
+int test_variadic_4 (int n, ...)
+{
+  if (n > 0)
+    return test_variadic_4 (n - 1, 42);
+  return 0;
+}
index b770e128bc689c39d568e612332f6608220c56ef..6b7d25cfabe6c576b0e13964813020fe7d6b7779 100644 (file)
@@ -11,7 +11,7 @@ void test(int flag)
     marker_B();
 
     /* Recurse, infinitely, as it happens: */
-    test(flag);
+    test(flag); /* { dg-warning "infinite recursion" } */
 
     marker_C();
   }
@@ -30,26 +30,26 @@ void mutual_test_1 (int flag)
 {
   marker_A ();
   if (flag)
-    mutual_test_2 (flag);
+    mutual_test_2 (flag); /* { dg-warning "infinite recursion" } */
 }
 
 void mutual_test_2 (int flag)
 {
   marker_B ();
   if (flag)
-    mutual_test_3 (flag);
+    mutual_test_3 (flag); /* { dg-warning "infinite recursion" } */
 }
 
 void mutual_test_3 (int flag)
 {
   marker_C ();
   if (flag)
-    mutual_test_4 (flag);
+    mutual_test_4 (flag); /* { dg-warning "infinite recursion" } */
 }
 
 void mutual_test_4 (int flag)
 {
   marker_D ();
   if (flag)
-    mutual_test_1 (flag);
+    mutual_test_1 (flag); /* { dg-warning "infinite recursion" } */
 }
index fce5437edd16e6ab2824d97086ca3747d85bf0f7..3813c9ae45ea8f44dbbe79b9fdebd46dbf470768 100644 (file)
@@ -3,5 +3,5 @@
 void recursive_free (void *ptr)
 {
   free (ptr); /* { dg-warning "double-'free' of 'ptr'" } */
-  recursive_free (ptr);
+  recursive_free (ptr); /* { dg-warning "infinite recursion" } */
 }
index aa576d086328950e4c8a15b2314f826a33b10a75..c2e6b2e16fc24287c55c17a5ad194b3d5dbbd419 100644 (file)
@@ -13,5 +13,5 @@ foo(_Float32 k) {
   f /= (_Complex char)__builtin_llround(g);
   k /= (cf32)__builtin_copysignf(0, i);
   bar(f + k);
-  foo(0);
+  foo(0); /* { dg-warning "infinite recursion" } */
 }
index 3dba870e4e96031ae0cfe2ee68c6b4d445e014e2..af8a2c4f9cc9208b9dda6b1bd049ef04fb61806e 100644 (file)
@@ -15,5 +15,5 @@ foo(u32 u, __int128 i) {
   u /= (_Complex short)s;
   u32 r = u + c;
   bar(r);
-  foo(0, 0);
+  foo(0, 0); /* { dg-warning "infinite recursion" } */
 }
index e3b453117791173a7bff8ec23ce1219b172da4b5..2ab2d4155ccff8f379b1ac60e92057ca2dae2d07 100644 (file)
@@ -5,5 +5,5 @@ struct vj {
 void
 setjmp (struct vj pl)
 {
-  setjmp (pl);
+  setjmp (pl); /* { dg-warning "infinite recursion" } */
 }