blob: 91a3cdbacd4cf14d1abf11e2a31102ec1c91c91c [file] [log] [blame]
/* -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))
{
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);
}