clang-tools 24.0.0git
SignalHandlerCheck.cpp
Go to the documentation of this file.
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-exception
6//
7//===----------------------------------------------------------------------===//
8
10#include "clang/ASTMatchers/ASTMatchFinder.h"
11#include "llvm/ADT/DepthFirstIterator.h"
12#include "llvm/ADT/STLExtras.h"
13
14using namespace clang::ast_matchers;
15
16namespace clang::tidy {
17
18// This is the minimal set of safe functions.
19// https://wiki.sei.cmu.edu/confluence/display/c/SIG30-C.+Call+only+asynchronous-safe+functions+within+signal+handlers
20constexpr StringRef MinimalConformingFunctions[] = {"signal", "abort", "_Exit",
21 "quick_exit"};
22
23// The POSIX-defined set of safe functions.
24// https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03
25// 'quick_exit' is added to the set additionally because it looks like the
26// mentioned POSIX specification was not updated after 'quick_exit' appeared
27// in the C11 standard.
28// Also, we want to keep the "minimal set" a subset of the "POSIX set".
29// The list is repeated in bugprone-signal-handler.rst and should be kept up to
30// date.
31// clang-format off
32constexpr StringRef POSIXConformingFunctions[] = {
33 "_Exit",
34 "_exit",
35 "abort",
36 "accept",
37 "access",
38 "aio_error",
39 "aio_return",
40 "aio_suspend",
41 "alarm",
42 "bind",
43 "cfgetispeed",
44 "cfgetospeed",
45 "cfsetispeed",
46 "cfsetospeed",
47 "chdir",
48 "chmod",
49 "chown",
50 "clock_gettime",
51 "close",
52 "connect",
53 "creat",
54 "dup",
55 "dup2",
56 "execl",
57 "execle",
58 "execv",
59 "execve",
60 "faccessat",
61 "fchdir",
62 "fchmod",
63 "fchmodat",
64 "fchown",
65 "fchownat",
66 "fcntl",
67 "fdatasync",
68 "fexecve",
69 "ffs",
70 "fork",
71 "fstat",
72 "fstatat",
73 "fsync",
74 "ftruncate",
75 "futimens",
76 "getegid",
77 "geteuid",
78 "getgid",
79 "getgroups",
80 "getpeername",
81 "getpgrp",
82 "getpid",
83 "getppid",
84 "getsockname",
85 "getsockopt",
86 "getuid",
87 "htonl",
88 "htons",
89 "kill",
90 "link",
91 "linkat",
92 "listen",
93 "longjmp",
94 "lseek",
95 "lstat",
96 "memccpy",
97 "memchr",
98 "memcmp",
99 "memcpy",
100 "memmove",
101 "memset",
102 "mkdir",
103 "mkdirat",
104 "mkfifo",
105 "mkfifoat",
106 "mknod",
107 "mknodat",
108 "ntohl",
109 "ntohs",
110 "open",
111 "openat",
112 "pause",
113 "pipe",
114 "poll",
115 "posix_trace_event",
116 "pselect",
117 "pthread_kill",
118 "pthread_self",
119 "pthread_sigmask",
120 "quick_exit",
121 "raise",
122 "read",
123 "readlink",
124 "readlinkat",
125 "recv",
126 "recvfrom",
127 "recvmsg",
128 "rename",
129 "renameat",
130 "rmdir",
131 "select",
132 "sem_post",
133 "send",
134 "sendmsg",
135 "sendto",
136 "setgid",
137 "setpgid",
138 "setsid",
139 "setsockopt",
140 "setuid",
141 "shutdown",
142 "sigaction",
143 "sigaddset",
144 "sigdelset",
145 "sigemptyset",
146 "sigfillset",
147 "sigismember",
148 "siglongjmp",
149 "signal",
150 "sigpause",
151 "sigpending",
152 "sigprocmask",
153 "sigqueue",
154 "sigset",
155 "sigsuspend",
156 "sleep",
157 "sockatmark",
158 "socket",
159 "socketpair",
160 "stat",
161 "stpcpy",
162 "stpncpy",
163 "strcat",
164 "strchr",
165 "strcmp",
166 "strcpy",
167 "strcspn",
168 "strlen",
169 "strncat",
170 "strncmp",
171 "strncpy",
172 "strnlen",
173 "strpbrk",
174 "strrchr",
175 "strspn",
176 "strstr",
177 "strtok_r",
178 "symlink",
179 "symlinkat",
180 "tcdrain",
181 "tcflow",
182 "tcflush",
183 "tcgetattr",
184 "tcgetpgrp",
185 "tcsendbreak",
186 "tcsetattr",
187 "tcsetpgrp",
188 "time",
189 "timer_getoverrun",
190 "timer_gettime",
191 "timer_settime",
192 "times",
193 "umask",
194 "uname",
195 "unlink",
196 "unlinkat",
197 "utime",
198 "utimensat",
199 "utimes",
200 "wait",
201 "waitpid",
202 "wcpcpy",
203 "wcpncpy",
204 "wcscat",
205 "wcschr",
206 "wcscmp",
207 "wcscpy",
208 "wcscspn",
209 "wcslen",
210 "wcsncat",
211 "wcsncmp",
212 "wcsncpy",
213 "wcsnlen",
214 "wcspbrk",
215 "wcsrchr",
216 "wcsspn",
217 "wcsstr",
218 "wcstok",
219 "wmemchr",
220 "wmemcmp",
221 "wmemcpy",
222 "wmemmove",
223 "wmemset",
224 "write"
225};
226// clang-format on
227
228template <>
230 bugprone::SignalHandlerCheck::AsyncSafeFunctionSetKind> {
231 static llvm::ArrayRef<std::pair<
234 static constexpr std::pair<
236 Mapping[] = {
238 "minimal"},
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) library
250/// 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 way
254 // 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.c
259 // #include <sysheader.h>
260
261 // Next most common case will be extern declaration.
262 // Can't catch this with either approach.
263 // b.c
264 // extern void sysfunc(void);
265
266 // Canonical declaration is the first found declaration, so this works.
267 // c.c
268 // #include <sysheader.h>
269 // extern void sysfunc(void); // redecl won't matter
270
271 // This does not work with canonical declaration.
272 // Probably this is not a frequently used case but may happen (the first
273 // declaration can be in a non-system header for example).
274 // d.c
275 // 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" prefix
284/// 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 is
302/// 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 order
304/// 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} // namespace
333
335 ClangTidyContext *Context)
336 : ClangTidyCheck(Name, Context),
337 AsyncSafeFunctionSet(Options.get("AsyncSafeFunctionSet",
339 if (AsyncSafeFunctionSet == AsyncSafeFunctionSetKind::Minimal)
340 ConformingFunctions.insert_range(MinimalConformingFunctions);
341 else
342 ConformingFunctions.insert_range(POSIXConformingFunctions);
343}
344
346 Options.store(Opts, "AsyncSafeFunctionSet", AsyncSafeFunctionSet);
347}
348
350 const LangOptions &LangOpts) const {
351 return !LangOpts.CPlusPlus17;
352}
353
354void SignalHandlerCheck::registerMatchers(MatchFinder *Finder) {
355 const auto SignalFunction =
356 functionDecl(hasAnyName("::signal", "::std::signal"), parameterCountIs(2),
357 isStandard());
358 auto HandlerExpr =
359 declRefExpr(hasDeclaration(functionDecl().bind("handler_decl")),
360 unless(isExpandedFromMacro("SIG_IGN")),
361 unless(isExpandedFromMacro("SIG_DFL")))
362 .bind("handler_expr");
363 auto HandlerLambda = cxxMemberCallExpr(
364 on(expr(ignoringParenImpCasts(lambdaExpr().bind("handler_lambda")))));
365 Finder->addMatcher(callExpr(callee(SignalFunction),
366 hasArgument(1, anyOf(HandlerExpr, HandlerLambda)))
367 .bind("register_call"),
368 this);
369}
370
371void SignalHandlerCheck::check(const MatchFinder::MatchResult &Result) {
372 if (const auto *HandlerLambda =
373 Result.Nodes.getNodeAs<LambdaExpr>("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>("handler_decl");
382 const auto *HandlerExpr = Result.Nodes.getNodeAs<DeclRefExpr>("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 const CallGraphNode *HandlerNode =
406 CG.getNode(HandlerDecl->getCanonicalDecl());
407 assert(HandlerNode &&
408 "Handler with body should be present in the call graph.");
409 // Start from signal handler and visit every function call.
410 auto Itr = llvm::df_begin(HandlerNode), ItrE = llvm::df_end(HandlerNode);
411 while (Itr != ItrE) {
412 const auto *CallF = dyn_cast<FunctionDecl>((*Itr)->getDecl());
413 const unsigned int PathL = Itr.getPathLength();
414 if (CallF) {
415 // A signal handler or a function transitively reachable from the signal
416 // handler was found to be unsafe.
417 // Generate notes for the whole call chain (including the signal handler
418 // registration).
419 const Expr *CallOrRef = (PathL > 1)
420 ? findCallExpr(Itr.getPath(PathL - 2), *Itr)
421 : HandlerExpr;
422 auto ChainReporter = [this, &Itr, HandlerExpr](bool SkipPathEnd) {
423 reportHandlerChain(Itr, HandlerExpr, SkipPathEnd);
424 };
425 // If problems were found in a function (`CallF`), skip the analysis of
426 // functions that are called from it.
427 if (checkFunction(CallF, CallOrRef, ChainReporter))
428 Itr.skipChildren();
429 else
430 ++Itr;
431 } else {
432 ++Itr;
433 }
434 }
435}
436
437bool SignalHandlerCheck::checkFunction(
438 const FunctionDecl *FD, const Expr *CallOrRef,
439 llvm::function_ref<void(bool)> ChainReporter) {
440 const bool FunctionIsCalled = isa<CallExpr>(CallOrRef);
441
442 if (isStandardFunction(FD)) {
443 if (!isStandardFunctionAsyncSafe(FD)) {
444 diag(CallOrRef->getBeginLoc(), "standard function %0 may not be "
445 "asynchronous-safe; "
446 "%select{using it as|calling it from}1 "
447 "a signal handler may be dangerous")
448 << FD << FunctionIsCalled << CallOrRef->getSourceRange();
449 if (ChainReporter)
450 ChainReporter(/*SkipPathEnd=*/true);
451 return true;
452 }
453 return false;
454 }
455
456 if (!FD->hasBody()) {
457 diag(CallOrRef->getBeginLoc(), "cannot verify that external function %0 is "
458 "asynchronous-safe; "
459 "%select{using it as|calling it from}1 "
460 "a signal handler may be dangerous")
461 << FD << FunctionIsCalled << CallOrRef->getSourceRange();
462 if (ChainReporter)
463 ChainReporter(/*SkipPathEnd=*/true);
464 return true;
465 }
466
467 if (getLangOpts().CPlusPlus)
468 return checkFunctionCPP14(FD, CallOrRef, ChainReporter);
469
470 return false;
471}
472
473bool SignalHandlerCheck::checkFunctionCPP14(
474 const FunctionDecl *FD, const Expr *CallOrRef,
475 llvm::function_ref<void(bool)> ChainReporter) {
476 if (!FD->isExternC()) {
477 diag(CallOrRef->getBeginLoc(),
478 "functions without C linkage are not allowed as signal "
479 "handler (until C++17)");
480 if (ChainReporter)
481 ChainReporter(/*SkipPathEnd=*/true);
482 return true;
483 }
484
485 const FunctionDecl *FBody = nullptr;
486 const Stmt *BodyS = FD->getBody(FBody);
487 if (!BodyS)
488 return false;
489
490 bool StmtProblemsFound = false;
491 ASTContext &Ctx = FBody->getASTContext();
492 const auto Matches =
493 match(decl(forEachDescendant(stmt().bind("stmt"))), *FBody, Ctx);
494 for (const auto &Match : Matches) {
495 const auto *FoundS = Match.getNodeAs<Stmt>("stmt");
496 if (isCXXOnlyStmt(FoundS)) {
497 const SourceRange R = getSourceRangeOfStmt(FoundS, Ctx);
498 if (R.isInvalid())
499 continue;
500 diag(R.getBegin(),
501 "C++-only construct is not allowed in signal handler (until C++17)")
502 << R;
503 diag(R.getBegin(), "internally, the statement is parsed as a '%0'",
504 DiagnosticIDs::Remark)
505 << FoundS->getStmtClassName();
506 if (ChainReporter)
507 ChainReporter(/*SkipPathEnd=*/false);
508 StmtProblemsFound = true;
509 }
510 }
511
512 return StmtProblemsFound;
513}
514
515bool SignalHandlerCheck::isStandardFunctionAsyncSafe(
516 const FunctionDecl *FD) const {
517 assert(isStandardFunction(FD));
518
519 const IdentifierInfo *II = FD->getIdentifier();
520 // Unnamed functions are not explicitly allowed.
521 // C++ std operators may be unsafe and not within the
522 // "common subset of C and C++".
523 if (!II)
524 return false;
525
526 if (!FD->isInStdNamespace() && !FD->isGlobal())
527 return false;
528
529 if (ConformingFunctions.contains(II->getName()))
530 return true;
531
532 return false;
533}
534
535void SignalHandlerCheck::reportHandlerChain(
536 const llvm::df_iterator<const CallGraphNode *> &Itr,
537 const DeclRefExpr *HandlerRef, bool SkipPathEnd) {
538 int CallLevel = Itr.getPathLength() - 2;
539 assert(CallLevel >= -1 && "Empty iterator?");
540
541 const CallGraphNode *Caller = Itr.getPath(CallLevel + 1), *Callee = nullptr;
542 while (CallLevel >= 0) {
543 Callee = Caller;
544 Caller = Itr.getPath(CallLevel);
545 const Expr *CE = findCallExpr(Caller, Callee);
546 if (SkipPathEnd)
547 SkipPathEnd = false;
548 else
549 diag(CE->getBeginLoc(), "function %0 called here from %1",
550 DiagnosticIDs::Note)
551 << cast<FunctionDecl>(Callee->getDecl())
552 << cast<FunctionDecl>(Caller->getDecl());
553 --CallLevel;
554 }
555
556 if (!SkipPathEnd)
557 diag(HandlerRef->getBeginLoc(),
558 "function %0 registered here as signal handler", DiagnosticIDs::Note)
559 << cast<FunctionDecl>(Caller->getDecl())
560 << HandlerRef->getSourceRange();
561}
562
563} // namespace bugprone
564} // namespace clang::tidy
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
SignalHandlerCheck(StringRef Name, ClangTidyContext *Context)
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
std::vector< std::string > match(const SymbolIndex &I, const FuzzyFindRequest &Req, bool *Incomplete)
static Expr * findCallExpr(const CallGraphNode *Caller, const CallGraphNode *Callee)
Given a call graph node of a Caller function and a Callee that is called from Caller,...
static SourceRange getSourceRangeOfStmt(const Stmt *S, ASTContext &Ctx)
static bool isCXXOnlyStmt(const Stmt *S)
Check if a statement is "C++-only".
static bool isStandardFunction(const FunctionDecl *FD)
Returns if a function is declared inside a system header.
cppcoreguidelines::ProBoundsAvoidUncheckedContainerAccessCheck P
constexpr StringRef MinimalConformingFunctions[]
constexpr StringRef POSIXConformingFunctions[]
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...