565 lines
16 KiB
C++
565 lines
16 KiB
C++
//===--- 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 {
|
|
namespace 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 makeArrayRef(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 tidy
|
|
} // namespace clang
|