214 lines
5.8 KiB
C++
214 lines
5.8 KiB
C++
/* -Winfinite-recursion support.
|
|
Copyright (C) 2021-2025 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:
|
|
bool gate (function *) final override { return warn_infinite_recursion; }
|
|
|
|
unsigned int execute (function *) final override;
|
|
|
|
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))
|
|
{
|
|
const char *cname
|
|
= IDENTIFIER_POINTER (DECL_NAME (current_function_decl));
|
|
/* Don't warn about gnu_inline extern inline function
|
|
like strcpy calling __builtin_strcpy, that is fine,
|
|
if some call is made (the builtin isn't expanded inline),
|
|
a call is made to the external definition. */
|
|
if (!(DECL_DECLARED_INLINE_P (current_function_decl)
|
|
&& DECL_EXTERNAL (current_function_decl))
|
|
|| strcmp (name, cname) == 0)
|
|
{
|
|
/* 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);
|
|
}
|