| 1 | //===--- SignalHandlerCheck.cpp - clang-tidy ------------------------------===// |
| 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-exception |
| 6 | // |
| 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+handlers |
| 16 | constexpr 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_03 |
| 21 | // 'quick_exit' is added to the set additionally because it looks like the |
| 22 | // mentioned POSIX specification was not updated after 'quick_exit' appeared |
| 23 | // 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 to |
| 26 | // date. |
| 27 | // clang-format off |
| 28 | constexpr 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 on |
| 223 | |
| 224 | using namespace clang::ast_matchers; |
| 225 | |
| 226 | namespace clang::tidy { |
| 227 | |
| 228 | template <> |
| 229 | struct 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 | |
| 246 | namespace bugprone { |
| 247 | |
| 248 | namespace { |
| 249 | |
| 250 | /// Returns if a function is declared inside a system header. |
| 251 | /// These functions are considered to be "standard" (system-provided) library |
| 252 | /// functions. |
| 253 | bool isStandardFunction(const FunctionDecl *FD) { |
| 254 | // Find a possible redeclaration in system header. |
| 255 | // FIXME: Looking at the canonical declaration is not the most exact way |
| 256 | // to do this. |
| 257 | |
| 258 | // Most common case will be inclusion directly from a header. |
| 259 | // This works fine by using canonical declaration. |
| 260 | // a.c |
| 261 | // #include <sysheader.h> |
| 262 | |
| 263 | // Next most common case will be extern declaration. |
| 264 | // Can't catch this with either approach. |
| 265 | // b.c |
| 266 | // extern void sysfunc(void); |
| 267 | |
| 268 | // Canonical declaration is the first found declaration, so this works. |
| 269 | // c.c |
| 270 | // #include <sysheader.h> |
| 271 | // extern void sysfunc(void); // redecl won't matter |
| 272 | |
| 273 | // This does not work with canonical declaration. |
| 274 | // Probably this is not a frequently used case but may happen (the first |
| 275 | // declaration can be in a non-system header for example). |
| 276 | // d.c |
| 277 | // extern void sysfunc(void); // Canonical declaration, not in system header. |
| 278 | // #include <sysheader.h> |
| 279 | |
| 280 | return FD->getASTContext().getSourceManager().isInSystemHeader( |
| 281 | FD->getCanonicalDecl()->getLocation()); |
| 282 | } |
| 283 | |
| 284 | /// Check if a statement is "C++-only". |
| 285 | /// This includes all statements that have a class name with "CXX" prefix |
| 286 | /// and every other statement that is declared in file ExprCXX.h. |
| 287 | bool isCXXOnlyStmt(const Stmt *S) { |
| 288 | StringRef Name = S->getStmtClassName(); |
| 289 | if (Name.starts_with(Prefix: "CXX" )) |
| 290 | return true; |
| 291 | // Check for all other class names in ExprCXX.h that have no 'CXX' prefix. |
| 292 | return isa<ArrayTypeTraitExpr, BuiltinBitCastExpr, CUDAKernelCallExpr, |
| 293 | CoawaitExpr, CoreturnStmt, CoroutineBodyStmt, CoroutineSuspendExpr, |
| 294 | CoyieldExpr, DependentCoawaitExpr, DependentScopeDeclRefExpr, |
| 295 | ExprWithCleanups, ExpressionTraitExpr, FunctionParmPackExpr, |
| 296 | LambdaExpr, MSDependentExistsStmt, MSPropertyRefExpr, |
| 297 | MSPropertySubscriptExpr, MaterializeTemporaryExpr, OverloadExpr, |
| 298 | PackExpansionExpr, SizeOfPackExpr, SubstNonTypeTemplateParmExpr, |
| 299 | SubstNonTypeTemplateParmPackExpr, TypeTraitExpr, |
| 300 | UserDefinedLiteral>(Val: S); |
| 301 | } |
| 302 | |
| 303 | /// Given a call graph node of a \p Caller function and a \p Callee that is |
| 304 | /// called from \p Caller, get a \c CallExpr of the corresponding function call. |
| 305 | /// It is unspecified which call is found if multiple calls exist, but the order |
| 306 | /// should be deterministic (depend only on the AST). |
| 307 | Expr *findCallExpr(const CallGraphNode *Caller, const CallGraphNode *Callee) { |
| 308 | const auto *FoundCallee = llvm::find_if( |
| 309 | Range: Caller->callees(), P: [Callee](const CallGraphNode::CallRecord &Call) { |
| 310 | return Call.Callee == Callee; |
| 311 | }); |
| 312 | assert(FoundCallee != Caller->end() && |
| 313 | "Callee should be called from the caller function here." ); |
| 314 | return FoundCallee->CallExpr; |
| 315 | } |
| 316 | |
| 317 | SourceRange getSourceRangeOfStmt(const Stmt *S, ASTContext &Ctx) { |
| 318 | ParentMapContext &PM = Ctx.getParentMapContext(); |
| 319 | DynTypedNode P = DynTypedNode::create(Node: *S); |
| 320 | while (P.getSourceRange().isInvalid()) { |
| 321 | DynTypedNodeList PL = PM.getParents(Node: P); |
| 322 | if (PL.size() != 1) |
| 323 | return {}; |
| 324 | P = PL[0]; |
| 325 | } |
| 326 | return P.getSourceRange(); |
| 327 | } |
| 328 | |
| 329 | AST_MATCHER(FunctionDecl, isStandardFunction) { |
| 330 | return isStandardFunction(FD: &Node); |
| 331 | } |
| 332 | |
| 333 | } // namespace |
| 334 | |
| 335 | SignalHandlerCheck::SignalHandlerCheck(StringRef Name, |
| 336 | ClangTidyContext *Context) |
| 337 | : ClangTidyCheck(Name, Context), |
| 338 | AsyncSafeFunctionSet(Options.get(LocalName: "AsyncSafeFunctionSet" , |
| 339 | Default: AsyncSafeFunctionSetKind::POSIX)) { |
| 340 | if (AsyncSafeFunctionSet == AsyncSafeFunctionSetKind::Minimal) |
| 341 | ConformingFunctions.insert_range(R: MinimalConformingFunctions); |
| 342 | else |
| 343 | ConformingFunctions.insert_range(R: POSIXConformingFunctions); |
| 344 | } |
| 345 | |
| 346 | void SignalHandlerCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
| 347 | Options.store(Options&: Opts, LocalName: "AsyncSafeFunctionSet" , Value: AsyncSafeFunctionSet); |
| 348 | } |
| 349 | |
| 350 | bool SignalHandlerCheck::isLanguageVersionSupported( |
| 351 | const LangOptions &LangOpts) const { |
| 352 | return !LangOpts.CPlusPlus17; |
| 353 | } |
| 354 | |
| 355 | void SignalHandlerCheck::registerMatchers(MatchFinder *Finder) { |
| 356 | auto SignalFunction = functionDecl(hasAnyName("::signal" , "::std::signal" ), |
| 357 | parameterCountIs(N: 2), isStandardFunction()); |
| 358 | auto HandlerExpr = |
| 359 | declRefExpr(hasDeclaration(InnerMatcher: functionDecl().bind(ID: "handler_decl" )), |
| 360 | unless(isExpandedFromMacro(MacroName: "SIG_IGN" )), |
| 361 | unless(isExpandedFromMacro(MacroName: "SIG_DFL" ))) |
| 362 | .bind(ID: "handler_expr" ); |
| 363 | auto HandlerLambda = cxxMemberCallExpr( |
| 364 | on(InnerMatcher: expr(ignoringParenImpCasts(InnerMatcher: lambdaExpr().bind(ID: "handler_lambda" ))))); |
| 365 | Finder->addMatcher(callExpr(callee(InnerMatcher: SignalFunction), |
| 366 | hasArgument(N: 1, InnerMatcher: anyOf(HandlerExpr, HandlerLambda))) |
| 367 | .bind(ID: "register_call" ), |
| 368 | this); |
| 369 | } |
| 370 | |
| 371 | void SignalHandlerCheck::check(const MatchFinder::MatchResult &Result) { |
| 372 | if (const auto *HandlerLambda = |
| 373 | Result.Nodes.getNodeAs<LambdaExpr>(ID: "handler_lambda" )) { |
| 374 | diag(HandlerLambda->getBeginLoc(), |
| 375 | "lambda function is not allowed as signal handler (until C++17)" ) |
| 376 | << HandlerLambda->getSourceRange(); |
| 377 | return; |
| 378 | } |
| 379 | |
| 380 | const auto *HandlerDecl = |
| 381 | Result.Nodes.getNodeAs<FunctionDecl>(ID: "handler_decl" ); |
| 382 | const auto *HandlerExpr = Result.Nodes.getNodeAs<DeclRefExpr>(ID: "handler_expr" ); |
| 383 | assert(Result.Nodes.getNodeAs<CallExpr>("register_call" ) && HandlerDecl && |
| 384 | HandlerExpr && "All of these should exist in a match here." ); |
| 385 | |
| 386 | if (CG.size() <= 1) { |
| 387 | // Call graph must be populated with the entire TU at the beginning. |
| 388 | // (It is possible to add a single function but the functions called from it |
| 389 | // are not analysed in this case.) |
| 390 | CG.addToCallGraph(const_cast<TranslationUnitDecl *>( |
| 391 | HandlerDecl->getTranslationUnitDecl())); |
| 392 | assert(CG.size() > 1 && |
| 393 | "There should be at least one function added to call graph." ); |
| 394 | } |
| 395 | |
| 396 | if (!HandlerDecl->hasBody()) { |
| 397 | // Check the handler function. |
| 398 | // The warning is placed to the signal handler registration. |
| 399 | // No need to display a call chain and no need for more checks. |
| 400 | (void)checkFunction(HandlerDecl, HandlerExpr, {}); |
| 401 | return; |
| 402 | } |
| 403 | |
| 404 | // FIXME: Update CallGraph::getNode to use canonical decl? |
| 405 | CallGraphNode *HandlerNode = 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(G: HandlerNode), ItrE = llvm::df_end(G: HandlerNode); |
| 410 | while (Itr != ItrE) { |
| 411 | const auto *CallF = dyn_cast<FunctionDecl>(Val: (*Itr)->getDecl()); |
| 412 | unsigned int PathL = Itr.getPathLength(); |
| 413 | if (CallF) { |
| 414 | // A signal handler or a function transitively reachable from the signal |
| 415 | // handler was found to be unsafe. |
| 416 | // Generate notes for the whole call chain (including the signal handler |
| 417 | // registration). |
| 418 | const Expr *CallOrRef = (PathL > 1) |
| 419 | ? findCallExpr(Caller: Itr.getPath(n: PathL - 2), Callee: *Itr) |
| 420 | : HandlerExpr; |
| 421 | auto ChainReporter = [this, &Itr, HandlerExpr](bool SkipPathEnd) { |
| 422 | reportHandlerChain(Itr, HandlerRef: HandlerExpr, SkipPathEnd); |
| 423 | }; |
| 424 | // If problems were found in a function (`CallF`), skip the analysis of |
| 425 | // functions that are called from it. |
| 426 | if (checkFunction(FD: CallF, CallOrRef, ChainReporter)) |
| 427 | Itr.skipChildren(); |
| 428 | else |
| 429 | ++Itr; |
| 430 | } else { |
| 431 | ++Itr; |
| 432 | } |
| 433 | } |
| 434 | } |
| 435 | |
| 436 | bool SignalHandlerCheck::checkFunction( |
| 437 | const FunctionDecl *FD, const Expr *CallOrRef, |
| 438 | std::function<void(bool)> ChainReporter) { |
| 439 | bool FunctionIsCalled = isa<CallExpr>(Val: 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 | |
| 472 | bool SignalHandlerCheck::checkFunctionCPP14( |
| 473 | const FunctionDecl *FD, const Expr *CallOrRef, |
| 474 | std::function<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(Definition&: FBody); |
| 486 | if (!BodyS) |
| 487 | return false; |
| 488 | |
| 489 | bool StmtProblemsFound = false; |
| 490 | ASTContext &Ctx = FBody->getASTContext(); |
| 491 | auto Matches = |
| 492 | match(Matcher: decl(forEachDescendant(stmt().bind(ID: "stmt" ))), Node: *FBody, Context&: Ctx); |
| 493 | for (const auto &Match : Matches) { |
| 494 | const auto *FoundS = Match.getNodeAs<Stmt>("stmt" ); |
| 495 | if (isCXXOnlyStmt(FoundS)) { |
| 496 | 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 | |
| 514 | bool 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 the |
| 521 | // "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.count(Key: II->getName())) |
| 529 | return true; |
| 530 | |
| 531 | return false; |
| 532 | } |
| 533 | |
| 534 | void SignalHandlerCheck::reportHandlerChain( |
| 535 | const llvm::df_iterator<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(n: CallLevel + 1), *Callee = nullptr; |
| 541 | while (CallLevel >= 0) { |
| 542 | Callee = Caller; |
| 543 | Caller = Itr.getPath(n: CallLevel); |
| 544 | const Expr *CE = findCallExpr(Caller, Callee); |
| 545 | if (SkipPathEnd) |
| 546 | SkipPathEnd = false; |
| 547 | else |
| 548 | diag(CE->getBeginLoc(), "function %0 called here from %1" , |
| 549 | DiagnosticIDs::Note) |
| 550 | << cast<FunctionDecl>(Val: Callee->getDecl()) |
| 551 | << cast<FunctionDecl>(Val: 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>(Val: Caller->getDecl()) |
| 559 | << HandlerRef->getSourceRange(); |
| 560 | } |
| 561 | |
| 562 | } // namespace bugprone |
| 563 | } // namespace clang::tidy |
| 564 | |