blob: a0b2afdb131032c9b8c52f431b9f8e26b83b3e0c [file] [log] [blame]
//===--- SignalHandlerCheck.cpp - clang-tidy ------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "SignalHandlerCheck.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "llvm/ADT/DepthFirstIterator.h"
#include "llvm/ADT/STLExtras.h"
// This is the minimal set of safe functions.
// https://wiki.sei.cmu.edu/confluence/display/c/SIG30-C.+Call+only+asynchronous-safe+functions+within+signal+handlers
constexpr llvm::StringLiteral MinimalConformingFunctions[] = {
"signal", "abort", "_Exit", "quick_exit"};
// The POSIX-defined set of safe functions.
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03
// 'quick_exit' is added to the set additionally because it looks like the
// mentioned POSIX specification was not updated after 'quick_exit' appeared
// in the C11 standard.
// Also, we want to keep the "minimal set" a subset of the "POSIX set".
// The list is repeated in bugprone-signal-handler.rst and should be kept up to date.
constexpr llvm::StringLiteral POSIXConformingFunctions[] = {
"_Exit",
"_exit",
"abort",
"accept",
"access",
"aio_error",
"aio_return",
"aio_suspend",
"alarm",
"bind",
"cfgetispeed",
"cfgetospeed",
"cfsetispeed",
"cfsetospeed",
"chdir",
"chmod",
"chown",
"clock_gettime",
"close",
"connect",
"creat",
"dup",
"dup2",
"execl",
"execle",
"execv",
"execve",
"faccessat",
"fchdir",
"fchmod",
"fchmodat",
"fchown",
"fchownat",
"fcntl",
"fdatasync",
"fexecve",
"ffs",
"fork",
"fstat",
"fstatat",
"fsync",
"ftruncate",
"futimens",
"getegid",
"geteuid",
"getgid",
"getgroups",
"getpeername",
"getpgrp",
"getpid",
"getppid",
"getsockname",
"getsockopt",
"getuid",
"htonl",
"htons",
"kill",
"link",
"linkat",
"listen",
"longjmp",
"lseek",
"lstat",
"memccpy",
"memchr",
"memcmp",
"memcpy",
"memmove",
"memset",
"mkdir",
"mkdirat",
"mkfifo",
"mkfifoat",
"mknod",
"mknodat",
"ntohl",
"ntohs",
"open",
"openat",
"pause",
"pipe",
"poll",
"posix_trace_event",
"pselect",
"pthread_kill",
"pthread_self",
"pthread_sigmask",
"quick_exit",
"raise",
"read",
"readlink",
"readlinkat",
"recv",
"recvfrom",
"recvmsg",
"rename",
"renameat",
"rmdir",
"select",
"sem_post",
"send",
"sendmsg",
"sendto",
"setgid",
"setpgid",
"setsid",
"setsockopt",
"setuid",
"shutdown",
"sigaction",
"sigaddset",
"sigdelset",
"sigemptyset",
"sigfillset",
"sigismember",
"siglongjmp",
"signal",
"sigpause",
"sigpending",
"sigprocmask",
"sigqueue",
"sigset",
"sigsuspend",
"sleep",
"sockatmark",
"socket",
"socketpair",
"stat",
"stpcpy",
"stpncpy",
"strcat",
"strchr",
"strcmp",
"strcpy",
"strcspn",
"strlen",
"strncat",
"strncmp",
"strncpy",
"strnlen",
"strpbrk",
"strrchr",
"strspn",
"strstr",
"strtok_r",
"symlink",
"symlinkat",
"tcdrain",
"tcflow",
"tcflush",
"tcgetattr",
"tcgetpgrp",
"tcsendbreak",
"tcsetattr",
"tcsetpgrp",
"time",
"timer_getoverrun",
"timer_gettime",
"timer_settime",
"times",
"umask",
"uname",
"unlink",
"unlinkat",
"utime",
"utimensat",
"utimes",
"wait",
"waitpid",
"wcpcpy",
"wcpncpy",
"wcscat",
"wcschr",
"wcscmp",
"wcscpy",
"wcscspn",
"wcslen",
"wcsncat",
"wcsncmp",
"wcsncpy",
"wcsnlen",
"wcspbrk",
"wcsrchr",
"wcsspn",
"wcsstr",
"wcstok",
"wmemchr",
"wmemcmp",
"wmemcpy",
"wmemmove",
"wmemset",
"write"};
using namespace clang::ast_matchers;
namespace clang::tidy {
template <>
struct OptionEnumMapping<
bugprone::SignalHandlerCheck::AsyncSafeFunctionSetKind> {
static llvm::ArrayRef<std::pair<
bugprone::SignalHandlerCheck::AsyncSafeFunctionSetKind, StringRef>>
getEnumMapping() {
static constexpr std::pair<
bugprone::SignalHandlerCheck::AsyncSafeFunctionSetKind, StringRef>
Mapping[] = {
{bugprone::SignalHandlerCheck::AsyncSafeFunctionSetKind::Minimal,
"minimal"},
{bugprone::SignalHandlerCheck::AsyncSafeFunctionSetKind::POSIX,
"POSIX"},
};
return ArrayRef(Mapping);
}
};
namespace bugprone {
namespace {
/// Returns if a function is declared inside a system header.
/// These functions are considered to be "standard" (system-provided) library
/// functions.
bool isStandardFunction(const FunctionDecl *FD) {
// Find a possible redeclaration in system header.
// FIXME: Looking at the canonical declaration is not the most exact way
// to do this.
// Most common case will be inclusion directly from a header.
// This works fine by using canonical declaration.
// a.c
// #include <sysheader.h>
// Next most common case will be extern declaration.
// Can't catch this with either approach.
// b.c
// extern void sysfunc(void);
// Canonical declaration is the first found declaration, so this works.
// c.c
// #include <sysheader.h>
// extern void sysfunc(void); // redecl won't matter
// This does not work with canonical declaration.
// Probably this is not a frequently used case but may happen (the first
// declaration can be in a non-system header for example).
// d.c
// extern void sysfunc(void); // Canonical declaration, not in system header.
// #include <sysheader.h>
return FD->getASTContext().getSourceManager().isInSystemHeader(
FD->getCanonicalDecl()->getLocation());
}
/// Check if a statement is "C++-only".
/// This includes all statements that have a class name with "CXX" prefix
/// and every other statement that is declared in file ExprCXX.h.
bool isCXXOnlyStmt(const Stmt *S) {
StringRef Name = S->getStmtClassName();
if (Name.startswith("CXX"))
return true;
// Check for all other class names in ExprCXX.h that have no 'CXX' prefix.
return isa<ArrayTypeTraitExpr, BuiltinBitCastExpr, CUDAKernelCallExpr,
CoawaitExpr, CoreturnStmt, CoroutineBodyStmt, CoroutineSuspendExpr,
CoyieldExpr, DependentCoawaitExpr, DependentScopeDeclRefExpr,
ExprWithCleanups, ExpressionTraitExpr, FunctionParmPackExpr,
LambdaExpr, MSDependentExistsStmt, MSPropertyRefExpr,
MSPropertySubscriptExpr, MaterializeTemporaryExpr, OverloadExpr,
PackExpansionExpr, SizeOfPackExpr, SubstNonTypeTemplateParmExpr,
SubstNonTypeTemplateParmPackExpr, TypeTraitExpr,
UserDefinedLiteral>(S);
}
/// Given a call graph node of a \p Caller function and a \p Callee that is
/// called from \p Caller, get a \c CallExpr of the corresponding function call.
/// It is unspecified which call is found if multiple calls exist, but the order
/// should be deterministic (depend only on the AST).
Expr *findCallExpr(const CallGraphNode *Caller, const CallGraphNode *Callee) {
auto FoundCallee = llvm::find_if(
Caller->callees(), [Callee](const CallGraphNode::CallRecord &Call) {
return Call.Callee == Callee;
});
assert(FoundCallee != Caller->end() &&
"Callee should be called from the caller function here.");
return FoundCallee->CallExpr;
}
SourceRange getSourceRangeOfStmt(const Stmt *S, ASTContext &Ctx) {
ParentMapContext &PM = Ctx.getParentMapContext();
DynTypedNode P = DynTypedNode::create(*S);
while (P.getSourceRange().isInvalid()) {
DynTypedNodeList PL = PM.getParents(P);
if (PL.size() != 1)
return {};
P = PL[0];
}
return P.getSourceRange();
}
} // namespace
AST_MATCHER(FunctionDecl, isStandardFunction) {
return isStandardFunction(&Node);
}
SignalHandlerCheck::SignalHandlerCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
AsyncSafeFunctionSet(Options.get("AsyncSafeFunctionSet",
AsyncSafeFunctionSetKind::POSIX)) {
if (AsyncSafeFunctionSet == AsyncSafeFunctionSetKind::Minimal) {
for (StringRef v : MinimalConformingFunctions)
ConformingFunctions.insert(v);
} else {
for (StringRef v : POSIXConformingFunctions)
ConformingFunctions.insert(v);
}
}
void SignalHandlerCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "AsyncSafeFunctionSet", AsyncSafeFunctionSet);
}
bool SignalHandlerCheck::isLanguageVersionSupported(
const LangOptions &LangOpts) const {
return !LangOpts.CPlusPlus17;
}
void SignalHandlerCheck::registerMatchers(MatchFinder *Finder) {
auto SignalFunction = functionDecl(hasAnyName("::signal", "::std::signal"),
parameterCountIs(2), isStandardFunction());
auto HandlerExpr =
declRefExpr(hasDeclaration(functionDecl().bind("handler_decl")),
unless(isExpandedFromMacro("SIG_IGN")),
unless(isExpandedFromMacro("SIG_DFL")))
.bind("handler_expr");
auto HandlerLambda = cxxMemberCallExpr(
on(expr(ignoringParenImpCasts(lambdaExpr().bind("handler_lambda")))));
Finder->addMatcher(callExpr(callee(SignalFunction),
hasArgument(1, anyOf(HandlerExpr, HandlerLambda)))
.bind("register_call"),
this);
}
void SignalHandlerCheck::check(const MatchFinder::MatchResult &Result) {
if (const auto *HandlerLambda =
Result.Nodes.getNodeAs<LambdaExpr>("handler_lambda")) {
diag(HandlerLambda->getBeginLoc(),
"lambda function is not allowed as signal handler (until C++17)")
<< HandlerLambda->getSourceRange();
return;
}
const auto *HandlerDecl =
Result.Nodes.getNodeAs<FunctionDecl>("handler_decl");
const auto *HandlerExpr = Result.Nodes.getNodeAs<DeclRefExpr>("handler_expr");
assert(Result.Nodes.getNodeAs<CallExpr>("register_call") && HandlerDecl &&
HandlerExpr && "All of these should exist in a match here.");
if (CG.size() <= 1) {
// Call graph must be populated with the entire TU at the beginning.
// (It is possible to add a single function but the functions called from it
// are not analysed in this case.)
CG.addToCallGraph(const_cast<TranslationUnitDecl *>(
HandlerDecl->getTranslationUnitDecl()));
assert(CG.size() > 1 &&
"There should be at least one function added to call graph.");
}
if (!HandlerDecl->hasBody()) {
// Check the handler function.
// The warning is placed to the signal handler registration.
// No need to display a call chain and no need for more checks.
(void)checkFunction(HandlerDecl, HandlerExpr, {});
return;
}
// FIXME: Update CallGraph::getNode to use canonical decl?
CallGraphNode *HandlerNode = CG.getNode(HandlerDecl->getCanonicalDecl());
assert(HandlerNode &&
"Handler with body should be present in the call graph.");
// Start from signal handler and visit every function call.
auto Itr = llvm::df_begin(HandlerNode), ItrE = llvm::df_end(HandlerNode);
while (Itr != ItrE) {
const auto *CallF = dyn_cast<FunctionDecl>((*Itr)->getDecl());
unsigned int PathL = Itr.getPathLength();
if (CallF) {
// A signal handler or a function transitively reachable from the signal
// handler was found to be unsafe.
// Generate notes for the whole call chain (including the signal handler
// registration).
const Expr *CallOrRef = (PathL > 1)
? findCallExpr(Itr.getPath(PathL - 2), *Itr)
: HandlerExpr;
auto ChainReporter = [this, &Itr, HandlerExpr](bool SkipPathEnd) {
reportHandlerChain(Itr, HandlerExpr, SkipPathEnd);
};
// If problems were found in a function (`CallF`), skip the analysis of
// functions that are called from it.
if (checkFunction(CallF, CallOrRef, ChainReporter))
Itr.skipChildren();
else
++Itr;
} else {
++Itr;
}
}
}
bool SignalHandlerCheck::checkFunction(
const FunctionDecl *FD, const Expr *CallOrRef,
std::function<void(bool)> ChainReporter) {
bool FunctionIsCalled = isa<CallExpr>(CallOrRef);
if (isStandardFunction(FD)) {
if (!isStandardFunctionAsyncSafe(FD)) {
diag(CallOrRef->getBeginLoc(), "standard function %0 may not be "
"asynchronous-safe; "
"%select{using it as|calling it from}1 "
"a signal handler may be dangerous")
<< FD << FunctionIsCalled << CallOrRef->getSourceRange();
if (ChainReporter)
ChainReporter(/*SkipPathEnd=*/true);
return true;
}
return false;
}
if (!FD->hasBody()) {
diag(CallOrRef->getBeginLoc(), "cannot verify that external function %0 is "
"asynchronous-safe; "
"%select{using it as|calling it from}1 "
"a signal handler may be dangerous")
<< FD << FunctionIsCalled << CallOrRef->getSourceRange();
if (ChainReporter)
ChainReporter(/*SkipPathEnd=*/true);
return true;
}
if (getLangOpts().CPlusPlus)
return checkFunctionCPP14(FD, CallOrRef, ChainReporter);
return false;
}
bool SignalHandlerCheck::checkFunctionCPP14(
const FunctionDecl *FD, const Expr *CallOrRef,
std::function<void(bool)> ChainReporter) {
if (!FD->isExternC()) {
diag(CallOrRef->getBeginLoc(),
"functions without C linkage are not allowed as signal "
"handler (until C++17)");
if (ChainReporter)
ChainReporter(/*SkipPathEnd=*/true);
return true;
}
const FunctionDecl *FBody;
const Stmt *BodyS = FD->getBody(FBody);
if (!BodyS)
return false;
bool StmtProblemsFound = false;
ASTContext &Ctx = FBody->getASTContext();
auto Matches =
match(decl(forEachDescendant(stmt().bind("stmt"))), *FBody, Ctx);
for (const auto &Match : Matches) {
const auto *FoundS = Match.getNodeAs<Stmt>("stmt");
if (isCXXOnlyStmt(FoundS)) {
SourceRange R = getSourceRangeOfStmt(FoundS, Ctx);
if (R.isInvalid())
continue;
diag(R.getBegin(),
"C++-only construct is not allowed in signal handler (until C++17)")
<< R;
diag(R.getBegin(), "internally, the statement is parsed as a '%0'",
DiagnosticIDs::Remark)
<< FoundS->getStmtClassName();
if (ChainReporter)
ChainReporter(/*SkipPathEnd=*/false);
StmtProblemsFound = true;
}
}
return StmtProblemsFound;
}
bool SignalHandlerCheck::isStandardFunctionAsyncSafe(
const FunctionDecl *FD) const {
assert(isStandardFunction(FD));
const IdentifierInfo *II = FD->getIdentifier();
// Unnamed functions are not explicitly allowed.
// C++ std operators may be unsafe and not within the
// "common subset of C and C++".
if (!II)
return false;
if (!FD->isInStdNamespace() && !FD->isGlobal())
return false;
if (ConformingFunctions.count(II->getName()))
return true;
return false;
}
void SignalHandlerCheck::reportHandlerChain(
const llvm::df_iterator<clang::CallGraphNode *> &Itr,
const DeclRefExpr *HandlerRef, bool SkipPathEnd) {
int CallLevel = Itr.getPathLength() - 2;
assert(CallLevel >= -1 && "Empty iterator?");
const CallGraphNode *Caller = Itr.getPath(CallLevel + 1), *Callee = nullptr;
while (CallLevel >= 0) {
Callee = Caller;
Caller = Itr.getPath(CallLevel);
const Expr *CE = findCallExpr(Caller, Callee);
if (SkipPathEnd)
SkipPathEnd = false;
else
diag(CE->getBeginLoc(), "function %0 called here from %1",
DiagnosticIDs::Note)
<< cast<FunctionDecl>(Callee->getDecl())
<< cast<FunctionDecl>(Caller->getDecl());
--CallLevel;
}
if (!SkipPathEnd)
diag(HandlerRef->getBeginLoc(),
"function %0 registered here as signal handler", DiagnosticIDs::Note)
<< cast<FunctionDecl>(Caller->getDecl())
<< HandlerRef->getSourceRange();
}
} // namespace bugprone
} // namespace clang::tidy