123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562 |
- //===--- 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
|