|  | //===--- 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 {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.starts_with("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 = nullptr; | 
|  | 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 |