aboutsummaryrefslogtreecommitdiff
path: root/gcc/gimple-warn-recursion.cc
diff options
context:
space:
mode:
Diffstat (limited to 'gcc/gimple-warn-recursion.cc')
-rw-r--r--gcc/gimple-warn-recursion.cc202
1 files changed, 202 insertions, 0 deletions
diff --git a/gcc/gimple-warn-recursion.cc b/gcc/gimple-warn-recursion.cc
new file mode 100644
index 0000000..3a50d5e
--- /dev/null
+++ b/gcc/gimple-warn-recursion.cc
@@ -0,0 +1,202 @@
+/* -Winfinite-recursion support.
+ Copyright (C) 2021-2022 Free Software Foundation, Inc.
+ Contributed by Martin Sebor <msebor@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"
+#include "system.h"
+#include "coretypes.h"
+#include "backend.h"
+#include "tree.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "ssa.h"
+#include "diagnostic-core.h"
+// #include "tree-dfa.h"
+#include "attribs.h"
+#include "gimple-iterator.h"
+
+namespace {
+
+const pass_data warn_recursion_data =
+{
+ GIMPLE_PASS, /* type */
+ "*infinite-recursion", /* name */
+ OPTGROUP_NONE, /* optinfo_flags */
+ TV_NONE, /* tv_id */
+ PROP_ssa, /* properties_required */
+ 0, /* properties_provided */
+ 0, /* properties_destroyed */
+ 0, /* todo_flags_start */
+ 0, /* todo_flags_finish */
+};
+
+class pass_warn_recursion : public gimple_opt_pass
+{
+public:
+ pass_warn_recursion (gcc::context *);
+
+private:
+ virtual bool gate (function *) { return warn_infinite_recursion; }
+
+ virtual unsigned int execute (function *);
+
+ bool find_function_exit (basic_block);
+
+ /* Recursive calls found in M_FUNC. */
+ vec<gimple *> *m_calls;
+ /* Basic blocks already visited in the current function. */
+ bitmap m_visited;
+ /* The current function. */
+ function *m_func;
+ /* The current function code if it's (also) a built-in. */
+ built_in_function m_built_in;
+ /* True if M_FUNC is a noreturn function. */
+ bool noreturn_p;
+};
+
+/* Initialize the pass and its members. */
+
+pass_warn_recursion::pass_warn_recursion (gcc::context *ctxt)
+ : gimple_opt_pass (warn_recursion_data, ctxt),
+ m_calls (), m_visited (), m_func (), m_built_in (), noreturn_p ()
+{
+}
+
+/* Return true if there is path from BB to M_FUNC exit point along which
+ there is no (recursive) call to M_FUNC. */
+
+bool
+pass_warn_recursion::find_function_exit (basic_block bb)
+{
+ if (!bitmap_set_bit (m_visited, bb->index))
+ return false;
+
+ if (bb == EXIT_BLOCK_PTR_FOR_FN (m_func))
+ return true;
+
+ /* Iterate over statements in BB, looking for a call to FNDECL. */
+ for (auto si = gsi_start_bb (bb); !gsi_end_p (si); gsi_next_nondebug (&si))
+ {
+ gimple *stmt = gsi_stmt (si);
+ if (!is_gimple_call (stmt))
+ continue;
+
+ if (gimple_call_builtin_p (stmt, BUILT_IN_LONGJMP))
+ /* A longjmp breaks infinite recursion. */
+ return true;
+
+ if (tree fndecl = gimple_call_fndecl (stmt))
+ {
+ /* A throw statement breaks infinite recursion. */
+ tree id = DECL_NAME (fndecl);
+ const char *name = IDENTIFIER_POINTER (id);
+ if (startswith (name, "__cxa_throw"))
+ return true;
+ /* As does a call to POSIX siglongjmp. */
+ if (!strcmp (name, "siglongjmp"))
+ return true;
+
+ if (m_built_in && gimple_call_builtin_p (stmt, BUILT_IN_NORMAL)
+ && m_built_in == DECL_FUNCTION_CODE (fndecl))
+ {
+ /* The call is being made from the definition of a built-in
+ (e.g., in a replacement of one) to itself. */
+ m_calls->safe_push (stmt);
+ return false;
+ }
+ }
+
+ if (noreturn_p)
+ {
+ /* A noreturn call breaks infinite recursion. */
+ int flags = gimple_call_flags (stmt);
+ if (flags & ECF_NORETURN)
+ return true;
+ }
+
+ tree callee = gimple_call_fndecl (stmt);
+ if (!callee || m_func->decl != callee)
+ continue;
+
+ /* Add the recursive call to the vector and return false. */
+ m_calls->safe_push (stmt);
+ return false;
+ }
+
+ /* If no call to FNDECL has been found search all BB's successors. */
+ edge e;
+ edge_iterator ei;
+ FOR_EACH_EDGE (e, ei, bb->succs)
+ if (find_function_exit (e->dest))
+ return true;
+
+ return false;
+}
+
+
+/* Search FUNC for unconditionally infinitely recursive calls to self
+ and issue a warning if it is such a function. */
+
+unsigned int
+pass_warn_recursion::execute (function *func)
+{
+ auto_bitmap visited;
+ auto_vec<gimple *> calls;
+
+ m_visited = visited;
+ m_calls = &calls;
+ m_func = func;
+
+ /* Avoid diagnosing an apparently infinitely recursive function that
+ doesn't return where the infinite recursion might be avoided by
+ a call to another noreturn function. */
+ noreturn_p = lookup_attribute ("noreturn", DECL_ATTRIBUTES (m_func->decl));
+
+ if (fndecl_built_in_p (m_func->decl, BUILT_IN_NORMAL))
+ m_built_in = DECL_FUNCTION_CODE (m_func->decl);
+ else
+ m_built_in = BUILT_IN_NONE;
+
+ basic_block entry_bb = ENTRY_BLOCK_PTR_FOR_FN (func);
+
+ if (find_function_exit (entry_bb) || m_calls->length () == 0)
+ return 0;
+
+ if (warning_at (DECL_SOURCE_LOCATION (func->decl),
+ OPT_Winfinite_recursion,
+ "infinite recursion detected"))
+ for (auto stmt: *m_calls)
+ {
+ location_t loc = gimple_location (stmt);
+ if (loc == UNKNOWN_LOCATION)
+ continue;
+
+ inform (loc, "recursive call");
+ }
+
+ return 0;
+}
+
+} // namespace
+
+gimple_opt_pass *
+make_pass_warn_recursion (gcc::context *ctxt)
+{
+ return new pass_warn_recursion (ctxt);
+}