10#include "clang/ASTMatchers/ASTMatchFinder.h"
11#include "llvm/ADT/DepthFirstIterator.h"
12#include "llvm/ADT/STLExtras.h"
17 "signal",
"abort",
"_Exit",
"quick_exit"};
226 bugprone::SignalHandlerCheck::AsyncSafeFunctionSetKind> {
227 static llvm::ArrayRef<std::pair<
230 static constexpr std::pair<
249bool isStandardFunction(
const FunctionDecl *FD) {
276 return FD->getASTContext().getSourceManager().isInSystemHeader(
277 FD->getCanonicalDecl()->getLocation());
283bool isCXXOnlyStmt(
const Stmt *S) {
284 StringRef
Name = S->getStmtClassName();
285 if (
Name.starts_with(
"CXX"))
288 return isa<ArrayTypeTraitExpr, BuiltinBitCastExpr, CUDAKernelCallExpr,
289 CoawaitExpr, CoreturnStmt, CoroutineBodyStmt, CoroutineSuspendExpr,
290 CoyieldExpr, DependentCoawaitExpr, DependentScopeDeclRefExpr,
291 ExprWithCleanups, ExpressionTraitExpr, FunctionParmPackExpr,
292 LambdaExpr, MSDependentExistsStmt, MSPropertyRefExpr,
293 MSPropertySubscriptExpr, MaterializeTemporaryExpr, OverloadExpr,
294 PackExpansionExpr, SizeOfPackExpr, SubstNonTypeTemplateParmExpr,
295 SubstNonTypeTemplateParmPackExpr, TypeTraitExpr,
296 UserDefinedLiteral>(S);
303Expr *findCallExpr(
const CallGraphNode *Caller,
const CallGraphNode *Callee) {
304 auto FoundCallee = llvm::find_if(
305 Caller->callees(), [Callee](
const CallGraphNode::CallRecord &Call) {
306 return Call.Callee == Callee;
308 assert(FoundCallee != Caller->end() &&
309 "Callee should be called from the caller function here.");
310 return FoundCallee->CallExpr;
313SourceRange getSourceRangeOfStmt(
const Stmt *S, ASTContext &Ctx) {
314 ParentMapContext &PM = Ctx.getParentMapContext();
315 DynTypedNode P = DynTypedNode::create(*S);
316 while (P.getSourceRange().isInvalid()) {
317 DynTypedNodeList PL = PM.getParents(P);
322 return P.getSourceRange();
328 return isStandardFunction(&
Node);
334 AsyncSafeFunctionSet(Options.get(
"AsyncSafeFunctionSet",
338 ConformingFunctions.insert(v);
341 ConformingFunctions.insert(v);
346 Options.
store(Opts,
"AsyncSafeFunctionSet", AsyncSafeFunctionSet);
350 const LangOptions &LangOpts)
const {
351 return !LangOpts.CPlusPlus17;
355 auto SignalFunction = functionDecl(hasAnyName(
"::signal",
"::std::signal"),
356 parameterCountIs(2), isStandardFunction());
358 declRefExpr(hasDeclaration(functionDecl().bind(
"handler_decl")),
359 unless(isExpandedFromMacro(
"SIG_IGN")),
360 unless(isExpandedFromMacro(
"SIG_DFL")))
361 .bind(
"handler_expr");
362 auto HandlerLambda = cxxMemberCallExpr(
363 on(expr(ignoringParenImpCasts(lambdaExpr().bind(
"handler_lambda")))));
364 Finder->addMatcher(callExpr(callee(SignalFunction),
365 hasArgument(1, anyOf(HandlerExpr, HandlerLambda)))
366 .bind(
"register_call"),
371 if (
const auto *HandlerLambda =
372 Result.Nodes.getNodeAs<LambdaExpr>(
"handler_lambda")) {
373 diag(HandlerLambda->getBeginLoc(),
374 "lambda function is not allowed as signal handler (until C++17)")
375 << HandlerLambda->getSourceRange();
379 const auto *HandlerDecl =
380 Result.Nodes.getNodeAs<FunctionDecl>(
"handler_decl");
381 const auto *HandlerExpr = Result.Nodes.getNodeAs<DeclRefExpr>(
"handler_expr");
382 assert(Result.Nodes.getNodeAs<CallExpr>(
"register_call") && HandlerDecl &&
383 HandlerExpr &&
"All of these should exist in a match here.");
385 if (CG.size() <= 1) {
389 CG.addToCallGraph(
const_cast<TranslationUnitDecl *
>(
390 HandlerDecl->getTranslationUnitDecl()));
391 assert(CG.size() > 1 &&
392 "There should be at least one function added to call graph.");
395 if (!HandlerDecl->hasBody()) {
399 (void)checkFunction(HandlerDecl, HandlerExpr, {});
404 CallGraphNode *HandlerNode = CG.getNode(HandlerDecl->getCanonicalDecl());
405 assert(HandlerNode &&
406 "Handler with body should be present in the call graph.");
408 auto Itr = llvm::df_begin(HandlerNode), ItrE = llvm::df_end(HandlerNode);
409 while (Itr != ItrE) {
410 const auto *CallF = dyn_cast<FunctionDecl>((*Itr)->getDecl());
411 unsigned int PathL = Itr.getPathLength();
417 const Expr *CallOrRef = (PathL > 1)
418 ? findCallExpr(Itr.getPath(PathL - 2), *Itr)
420 auto ChainReporter = [
this, &Itr, HandlerExpr](
bool SkipPathEnd) {
421 reportHandlerChain(Itr, HandlerExpr, SkipPathEnd);
425 if (checkFunction(CallF, CallOrRef, ChainReporter))
435bool SignalHandlerCheck::checkFunction(
436 const FunctionDecl *FD,
const Expr *CallOrRef,
437 std::function<
void(
bool)> ChainReporter) {
438 bool FunctionIsCalled = isa<CallExpr>(CallOrRef);
440 if (isStandardFunction(FD)) {
441 if (!isStandardFunctionAsyncSafe(FD)) {
442 diag(CallOrRef->getBeginLoc(),
"standard function %0 may not be "
443 "asynchronous-safe; "
444 "%select{using it as|calling it from}1 "
445 "a signal handler may be dangerous")
446 << FD << FunctionIsCalled << CallOrRef->getSourceRange();
454 if (!FD->hasBody()) {
455 diag(CallOrRef->getBeginLoc(),
"cannot verify that external function %0 is "
456 "asynchronous-safe; "
457 "%select{using it as|calling it from}1 "
458 "a signal handler may be dangerous")
459 << FD << FunctionIsCalled << CallOrRef->getSourceRange();
466 return checkFunctionCPP14(FD, CallOrRef, ChainReporter);
471bool SignalHandlerCheck::checkFunctionCPP14(
472 const FunctionDecl *FD,
const Expr *CallOrRef,
473 std::function<
void(
bool)> ChainReporter) {
474 if (!FD->isExternC()) {
475 diag(CallOrRef->getBeginLoc(),
476 "functions without C linkage are not allowed as signal "
477 "handler (until C++17)");
483 const FunctionDecl *FBody =
nullptr;
484 const Stmt *BodyS = FD->getBody(FBody);
488 bool StmtProblemsFound =
false;
489 ASTContext &Ctx = FBody->getASTContext();
491 match(decl(forEachDescendant(stmt().bind(
"stmt"))), *FBody, Ctx);
492 for (
const auto &Match : Matches) {
493 const auto *FoundS = Match.getNodeAs<Stmt>(
"stmt");
494 if (isCXXOnlyStmt(FoundS)) {
495 SourceRange R = getSourceRangeOfStmt(FoundS, Ctx);
499 "C++-only construct is not allowed in signal handler (until C++17)")
501 diag(R.getBegin(),
"internally, the statement is parsed as a '%0'",
502 DiagnosticIDs::Remark)
503 << FoundS->getStmtClassName();
505 ChainReporter(
false);
506 StmtProblemsFound =
true;
510 return StmtProblemsFound;
513bool SignalHandlerCheck::isStandardFunctionAsyncSafe(
514 const FunctionDecl *FD)
const {
515 assert(isStandardFunction(FD));
517 const IdentifierInfo *II = FD->getIdentifier();
524 if (!FD->isInStdNamespace() && !FD->isGlobal())
527 if (ConformingFunctions.count(II->getName()))
533void SignalHandlerCheck::reportHandlerChain(
534 const llvm::df_iterator<clang::CallGraphNode *> &Itr,
535 const DeclRefExpr *HandlerRef,
bool SkipPathEnd) {
536 int CallLevel = Itr.getPathLength() - 2;
537 assert(CallLevel >= -1 &&
"Empty iterator?");
539 const CallGraphNode *Caller = Itr.getPath(CallLevel + 1), *Callee =
nullptr;
540 while (CallLevel >= 0) {
542 Caller = Itr.getPath(CallLevel);
543 const Expr *
CE = findCallExpr(Caller, Callee);
547 diag(
CE->getBeginLoc(),
"function %0 called here from %1",
549 << cast<FunctionDecl>(Callee->getDecl())
550 << cast<FunctionDecl>(Caller->getDecl());
555 diag(HandlerRef->getBeginLoc(),
556 "function %0 registered here as signal handler", DiagnosticIDs::Note)
557 << cast<FunctionDecl>(Caller->getDecl())
558 << HandlerRef->getSourceRange();
llvm::SmallString< 256U > Name
::clang::DynTypedNode Node
constexpr llvm::StringLiteral MinimalConformingFunctions[]
constexpr llvm::StringLiteral POSIXConformingFunctions[]
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
const LangOptions & getLangOpts() const
Returns the language options from the context.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
SignalHandlerCheck(StringRef Name, ClangTidyContext *Context)
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override
Override this to disable registering matchers and PP callbacks if an invalid language version is bein...
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
std::vector< std::string > match(const SymbolIndex &I, const FuzzyFindRequest &Req, bool *Incomplete)
AST_MATCHER(clang::VarDecl, hasConstantDeclaration)
llvm::StringMap< ClangTidyValue > OptionMap
static llvm::ArrayRef< std::pair< bugprone::SignalHandlerCheck::AsyncSafeFunctionSetKind, StringRef > > getEnumMapping()
This class should be specialized by any enum type that needs to be converted to and from an llvm::Str...