564 lines · cpp
1//===----------------------------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "SignalHandlerCheck.h"10#include "clang/ASTMatchers/ASTMatchFinder.h"11#include "llvm/ADT/DepthFirstIterator.h"12#include "llvm/ADT/STLExtras.h"13 14// This is the minimal set of safe functions.15// https://wiki.sei.cmu.edu/confluence/display/c/SIG30-C.+Call+only+asynchronous-safe+functions+within+signal+handlers16constexpr llvm::StringLiteral MinimalConformingFunctions[] = {17 "signal", "abort", "_Exit", "quick_exit"};18 19// The POSIX-defined set of safe functions.20// https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_0321// 'quick_exit' is added to the set additionally because it looks like the22// mentioned POSIX specification was not updated after 'quick_exit' appeared23// in the C11 standard.24// Also, we want to keep the "minimal set" a subset of the "POSIX set".25// The list is repeated in bugprone-signal-handler.rst and should be kept up to26// date.27// clang-format off28constexpr llvm::StringLiteral POSIXConformingFunctions[] = {29 "_Exit",30 "_exit",31 "abort",32 "accept",33 "access",34 "aio_error",35 "aio_return",36 "aio_suspend",37 "alarm",38 "bind",39 "cfgetispeed",40 "cfgetospeed",41 "cfsetispeed",42 "cfsetospeed",43 "chdir",44 "chmod",45 "chown",46 "clock_gettime",47 "close",48 "connect",49 "creat",50 "dup",51 "dup2",52 "execl",53 "execle",54 "execv",55 "execve",56 "faccessat",57 "fchdir",58 "fchmod",59 "fchmodat",60 "fchown",61 "fchownat",62 "fcntl",63 "fdatasync",64 "fexecve",65 "ffs",66 "fork",67 "fstat",68 "fstatat",69 "fsync",70 "ftruncate",71 "futimens",72 "getegid",73 "geteuid",74 "getgid",75 "getgroups",76 "getpeername",77 "getpgrp",78 "getpid",79 "getppid",80 "getsockname",81 "getsockopt",82 "getuid",83 "htonl",84 "htons",85 "kill",86 "link",87 "linkat",88 "listen",89 "longjmp",90 "lseek",91 "lstat",92 "memccpy",93 "memchr",94 "memcmp",95 "memcpy",96 "memmove",97 "memset",98 "mkdir",99 "mkdirat",100 "mkfifo",101 "mkfifoat",102 "mknod",103 "mknodat",104 "ntohl",105 "ntohs",106 "open",107 "openat",108 "pause",109 "pipe",110 "poll",111 "posix_trace_event",112 "pselect",113 "pthread_kill",114 "pthread_self",115 "pthread_sigmask",116 "quick_exit",117 "raise",118 "read",119 "readlink",120 "readlinkat",121 "recv",122 "recvfrom",123 "recvmsg",124 "rename",125 "renameat",126 "rmdir",127 "select",128 "sem_post",129 "send",130 "sendmsg",131 "sendto",132 "setgid",133 "setpgid",134 "setsid",135 "setsockopt",136 "setuid",137 "shutdown",138 "sigaction",139 "sigaddset",140 "sigdelset",141 "sigemptyset",142 "sigfillset",143 "sigismember",144 "siglongjmp",145 "signal",146 "sigpause",147 "sigpending",148 "sigprocmask",149 "sigqueue",150 "sigset",151 "sigsuspend",152 "sleep",153 "sockatmark",154 "socket",155 "socketpair",156 "stat",157 "stpcpy",158 "stpncpy",159 "strcat",160 "strchr",161 "strcmp",162 "strcpy",163 "strcspn",164 "strlen",165 "strncat",166 "strncmp",167 "strncpy",168 "strnlen",169 "strpbrk",170 "strrchr",171 "strspn",172 "strstr",173 "strtok_r",174 "symlink",175 "symlinkat",176 "tcdrain",177 "tcflow",178 "tcflush",179 "tcgetattr",180 "tcgetpgrp",181 "tcsendbreak",182 "tcsetattr",183 "tcsetpgrp",184 "time",185 "timer_getoverrun",186 "timer_gettime",187 "timer_settime",188 "times",189 "umask",190 "uname",191 "unlink",192 "unlinkat",193 "utime",194 "utimensat",195 "utimes",196 "wait",197 "waitpid",198 "wcpcpy",199 "wcpncpy",200 "wcscat",201 "wcschr",202 "wcscmp",203 "wcscpy",204 "wcscspn",205 "wcslen",206 "wcsncat",207 "wcsncmp",208 "wcsncpy",209 "wcsnlen",210 "wcspbrk",211 "wcsrchr",212 "wcsspn",213 "wcsstr",214 "wcstok",215 "wmemchr",216 "wmemcmp",217 "wmemcpy",218 "wmemmove",219 "wmemset",220 "write"221};222// clang-format on223 224using namespace clang::ast_matchers;225 226namespace clang::tidy {227 228template <>229struct OptionEnumMapping<230 bugprone::SignalHandlerCheck::AsyncSafeFunctionSetKind> {231 static llvm::ArrayRef<std::pair<232 bugprone::SignalHandlerCheck::AsyncSafeFunctionSetKind, StringRef>>233 getEnumMapping() {234 static constexpr std::pair<235 bugprone::SignalHandlerCheck::AsyncSafeFunctionSetKind, StringRef>236 Mapping[] = {237 {bugprone::SignalHandlerCheck::AsyncSafeFunctionSetKind::Minimal,238 "minimal"},239 {bugprone::SignalHandlerCheck::AsyncSafeFunctionSetKind::POSIX,240 "POSIX"},241 };242 return {Mapping};243 }244};245 246namespace bugprone {247 248/// Returns if a function is declared inside a system header.249/// These functions are considered to be "standard" (system-provided) library250/// functions.251static bool isStandardFunction(const FunctionDecl *FD) {252 // Find a possible redeclaration in system header.253 // FIXME: Looking at the canonical declaration is not the most exact way254 // to do this.255 256 // Most common case will be inclusion directly from a header.257 // This works fine by using canonical declaration.258 // a.c259 // #include <sysheader.h>260 261 // Next most common case will be extern declaration.262 // Can't catch this with either approach.263 // b.c264 // extern void sysfunc(void);265 266 // Canonical declaration is the first found declaration, so this works.267 // c.c268 // #include <sysheader.h>269 // extern void sysfunc(void); // redecl won't matter270 271 // This does not work with canonical declaration.272 // Probably this is not a frequently used case but may happen (the first273 // declaration can be in a non-system header for example).274 // d.c275 // extern void sysfunc(void); // Canonical declaration, not in system header.276 // #include <sysheader.h>277 278 return FD->getASTContext().getSourceManager().isInSystemHeader(279 FD->getCanonicalDecl()->getLocation());280}281 282/// Check if a statement is "C++-only".283/// This includes all statements that have a class name with "CXX" prefix284/// and every other statement that is declared in file ExprCXX.h.285static bool isCXXOnlyStmt(const Stmt *S) {286 const StringRef Name = S->getStmtClassName();287 if (Name.starts_with("CXX"))288 return true;289 // Check for all other class names in ExprCXX.h that have no 'CXX' prefix.290 return isa<ArrayTypeTraitExpr, BuiltinBitCastExpr, CUDAKernelCallExpr,291 CoawaitExpr, CoreturnStmt, CoroutineBodyStmt, CoroutineSuspendExpr,292 CoyieldExpr, DependentCoawaitExpr, DependentScopeDeclRefExpr,293 ExprWithCleanups, ExpressionTraitExpr, FunctionParmPackExpr,294 LambdaExpr, MSDependentExistsStmt, MSPropertyRefExpr,295 MSPropertySubscriptExpr, MaterializeTemporaryExpr, OverloadExpr,296 PackExpansionExpr, SizeOfPackExpr, SubstNonTypeTemplateParmExpr,297 SubstNonTypeTemplateParmPackExpr, TypeTraitExpr,298 UserDefinedLiteral>(S);299}300 301/// Given a call graph node of a \p Caller function and a \p Callee that is302/// called from \p Caller, get a \c CallExpr of the corresponding function call.303/// It is unspecified which call is found if multiple calls exist, but the order304/// should be deterministic (depend only on the AST).305static Expr *findCallExpr(const CallGraphNode *Caller,306 const CallGraphNode *Callee) {307 const auto *FoundCallee = llvm::find_if(308 Caller->callees(), [Callee](const CallGraphNode::CallRecord &Call) {309 return Call.Callee == Callee;310 });311 assert(FoundCallee != Caller->end() &&312 "Callee should be called from the caller function here.");313 return FoundCallee->CallExpr;314}315 316static SourceRange getSourceRangeOfStmt(const Stmt *S, ASTContext &Ctx) {317 ParentMapContext &PM = Ctx.getParentMapContext();318 DynTypedNode P = DynTypedNode::create(*S);319 while (P.getSourceRange().isInvalid()) {320 const DynTypedNodeList PL = PM.getParents(P);321 if (PL.size() != 1)322 return {};323 P = PL[0];324 }325 return P.getSourceRange();326}327 328namespace {329 330AST_MATCHER(FunctionDecl, isStandard) { return isStandardFunction(&Node); }331 332} // namespace333 334SignalHandlerCheck::SignalHandlerCheck(StringRef Name,335 ClangTidyContext *Context)336 : ClangTidyCheck(Name, Context),337 AsyncSafeFunctionSet(Options.get("AsyncSafeFunctionSet",338 AsyncSafeFunctionSetKind::POSIX)) {339 if (AsyncSafeFunctionSet == AsyncSafeFunctionSetKind::Minimal)340 ConformingFunctions.insert_range(MinimalConformingFunctions);341 else342 ConformingFunctions.insert_range(POSIXConformingFunctions);343}344 345void SignalHandlerCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {346 Options.store(Opts, "AsyncSafeFunctionSet", AsyncSafeFunctionSet);347}348 349bool SignalHandlerCheck::isLanguageVersionSupported(350 const LangOptions &LangOpts) const {351 return !LangOpts.CPlusPlus17;352}353 354void SignalHandlerCheck::registerMatchers(MatchFinder *Finder) {355 auto SignalFunction = functionDecl(hasAnyName("::signal", "::std::signal"),356 parameterCountIs(2), isStandard());357 auto HandlerExpr =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"),367 this);368}369 370void SignalHandlerCheck::check(const MatchFinder::MatchResult &Result) {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();376 return;377 }378 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.");384 385 if (CG.size() <= 1) {386 // Call graph must be populated with the entire TU at the beginning.387 // (It is possible to add a single function but the functions called from it388 // are not analysed in this case.)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.");393 }394 395 if (!HandlerDecl->hasBody()) {396 // Check the handler function.397 // The warning is placed to the signal handler registration.398 // No need to display a call chain and no need for more checks.399 (void)checkFunction(HandlerDecl, HandlerExpr, {});400 return;401 }402 403 // FIXME: Update CallGraph::getNode to use canonical decl?404 const CallGraphNode *HandlerNode =405 CG.getNode(HandlerDecl->getCanonicalDecl());406 assert(HandlerNode &&407 "Handler with body should be present in the call graph.");408 // Start from signal handler and visit every function call.409 auto Itr = llvm::df_begin(HandlerNode), ItrE = llvm::df_end(HandlerNode);410 while (Itr != ItrE) {411 const auto *CallF = dyn_cast<FunctionDecl>((*Itr)->getDecl());412 const unsigned int PathL = Itr.getPathLength();413 if (CallF) {414 // A signal handler or a function transitively reachable from the signal415 // handler was found to be unsafe.416 // Generate notes for the whole call chain (including the signal handler417 // registration).418 const Expr *CallOrRef = (PathL > 1)419 ? findCallExpr(Itr.getPath(PathL - 2), *Itr)420 : HandlerExpr;421 auto ChainReporter = [this, &Itr, HandlerExpr](bool SkipPathEnd) {422 reportHandlerChain(Itr, HandlerExpr, SkipPathEnd);423 };424 // If problems were found in a function (`CallF`), skip the analysis of425 // functions that are called from it.426 if (checkFunction(CallF, CallOrRef, ChainReporter))427 Itr.skipChildren();428 else429 ++Itr;430 } else {431 ++Itr;432 }433 }434}435 436bool SignalHandlerCheck::checkFunction(437 const FunctionDecl *FD, const Expr *CallOrRef,438 llvm::function_ref<void(bool)> ChainReporter) {439 const bool FunctionIsCalled = isa<CallExpr>(CallOrRef);440 441 if (isStandardFunction(FD)) {442 if (!isStandardFunctionAsyncSafe(FD)) {443 diag(CallOrRef->getBeginLoc(), "standard function %0 may not be "444 "asynchronous-safe; "445 "%select{using it as|calling it from}1 "446 "a signal handler may be dangerous")447 << FD << FunctionIsCalled << CallOrRef->getSourceRange();448 if (ChainReporter)449 ChainReporter(/*SkipPathEnd=*/true);450 return true;451 }452 return false;453 }454 455 if (!FD->hasBody()) {456 diag(CallOrRef->getBeginLoc(), "cannot verify that external function %0 is "457 "asynchronous-safe; "458 "%select{using it as|calling it from}1 "459 "a signal handler may be dangerous")460 << FD << FunctionIsCalled << CallOrRef->getSourceRange();461 if (ChainReporter)462 ChainReporter(/*SkipPathEnd=*/true);463 return true;464 }465 466 if (getLangOpts().CPlusPlus)467 return checkFunctionCPP14(FD, CallOrRef, ChainReporter);468 469 return false;470}471 472bool SignalHandlerCheck::checkFunctionCPP14(473 const FunctionDecl *FD, const Expr *CallOrRef,474 llvm::function_ref<void(bool)> ChainReporter) {475 if (!FD->isExternC()) {476 diag(CallOrRef->getBeginLoc(),477 "functions without C linkage are not allowed as signal "478 "handler (until C++17)");479 if (ChainReporter)480 ChainReporter(/*SkipPathEnd=*/true);481 return true;482 }483 484 const FunctionDecl *FBody = nullptr;485 const Stmt *BodyS = FD->getBody(FBody);486 if (!BodyS)487 return false;488 489 bool StmtProblemsFound = false;490 ASTContext &Ctx = FBody->getASTContext();491 auto Matches =492 match(decl(forEachDescendant(stmt().bind("stmt"))), *FBody, Ctx);493 for (const auto &Match : Matches) {494 const auto *FoundS = Match.getNodeAs<Stmt>("stmt");495 if (isCXXOnlyStmt(FoundS)) {496 const SourceRange R = getSourceRangeOfStmt(FoundS, Ctx);497 if (R.isInvalid())498 continue;499 diag(R.getBegin(),500 "C++-only construct is not allowed in signal handler (until C++17)")501 << R;502 diag(R.getBegin(), "internally, the statement is parsed as a '%0'",503 DiagnosticIDs::Remark)504 << FoundS->getStmtClassName();505 if (ChainReporter)506 ChainReporter(/*SkipPathEnd=*/false);507 StmtProblemsFound = true;508 }509 }510 511 return StmtProblemsFound;512}513 514bool SignalHandlerCheck::isStandardFunctionAsyncSafe(515 const FunctionDecl *FD) const {516 assert(isStandardFunction(FD));517 518 const IdentifierInfo *II = FD->getIdentifier();519 // Unnamed functions are not explicitly allowed.520 // C++ std operators may be unsafe and not within the521 // "common subset of C and C++".522 if (!II)523 return false;524 525 if (!FD->isInStdNamespace() && !FD->isGlobal())526 return false;527 528 if (ConformingFunctions.contains(II->getName()))529 return true;530 531 return false;532}533 534void SignalHandlerCheck::reportHandlerChain(535 const llvm::df_iterator<const clang::CallGraphNode *> &Itr,536 const DeclRefExpr *HandlerRef, bool SkipPathEnd) {537 int CallLevel = Itr.getPathLength() - 2;538 assert(CallLevel >= -1 && "Empty iterator?");539 540 const CallGraphNode *Caller = Itr.getPath(CallLevel + 1), *Callee = nullptr;541 while (CallLevel >= 0) {542 Callee = Caller;543 Caller = Itr.getPath(CallLevel);544 const Expr *CE = findCallExpr(Caller, Callee);545 if (SkipPathEnd)546 SkipPathEnd = false;547 else548 diag(CE->getBeginLoc(), "function %0 called here from %1",549 DiagnosticIDs::Note)550 << cast<FunctionDecl>(Callee->getDecl())551 << cast<FunctionDecl>(Caller->getDecl());552 --CallLevel;553 }554 555 if (!SkipPathEnd)556 diag(HandlerRef->getBeginLoc(),557 "function %0 registered here as signal handler", DiagnosticIDs::Note)558 << cast<FunctionDecl>(Caller->getDecl())559 << HandlerRef->getSourceRange();560}561 562} // namespace bugprone563} // namespace clang::tidy564