clang-tools 20.0.0git
SignalHandlerCheck.cpp
Go to the documentation of this file.
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
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
16constexpr 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 date.
26constexpr llvm::StringLiteral POSIXConformingFunctions[] = {
27 "_Exit",
28 "_exit",
29 "abort",
30 "accept",
31 "access",
32 "aio_error",
33 "aio_return",
34 "aio_suspend",
35 "alarm",
36 "bind",
37 "cfgetispeed",
38 "cfgetospeed",
39 "cfsetispeed",
40 "cfsetospeed",
41 "chdir",
42 "chmod",
43 "chown",
44 "clock_gettime",
45 "close",
46 "connect",
47 "creat",
48 "dup",
49 "dup2",
50 "execl",
51 "execle",
52 "execv",
53 "execve",
54 "faccessat",
55 "fchdir",
56 "fchmod",
57 "fchmodat",
58 "fchown",
59 "fchownat",
60 "fcntl",
61 "fdatasync",
62 "fexecve",
63 "ffs",
64 "fork",
65 "fstat",
66 "fstatat",
67 "fsync",
68 "ftruncate",
69 "futimens",
70 "getegid",
71 "geteuid",
72 "getgid",
73 "getgroups",
74 "getpeername",
75 "getpgrp",
76 "getpid",
77 "getppid",
78 "getsockname",
79 "getsockopt",
80 "getuid",
81 "htonl",
82 "htons",
83 "kill",
84 "link",
85 "linkat",
86 "listen",
87 "longjmp",
88 "lseek",
89 "lstat",
90 "memccpy",
91 "memchr",
92 "memcmp",
93 "memcpy",
94 "memmove",
95 "memset",
96 "mkdir",
97 "mkdirat",
98 "mkfifo",
99 "mkfifoat",
100 "mknod",
101 "mknodat",
102 "ntohl",
103 "ntohs",
104 "open",
105 "openat",
106 "pause",
107 "pipe",
108 "poll",
109 "posix_trace_event",
110 "pselect",
111 "pthread_kill",
112 "pthread_self",
113 "pthread_sigmask",
114 "quick_exit",
115 "raise",
116 "read",
117 "readlink",
118 "readlinkat",
119 "recv",
120 "recvfrom",
121 "recvmsg",
122 "rename",
123 "renameat",
124 "rmdir",
125 "select",
126 "sem_post",
127 "send",
128 "sendmsg",
129 "sendto",
130 "setgid",
131 "setpgid",
132 "setsid",
133 "setsockopt",
134 "setuid",
135 "shutdown",
136 "sigaction",
137 "sigaddset",
138 "sigdelset",
139 "sigemptyset",
140 "sigfillset",
141 "sigismember",
142 "siglongjmp",
143 "signal",
144 "sigpause",
145 "sigpending",
146 "sigprocmask",
147 "sigqueue",
148 "sigset",
149 "sigsuspend",
150 "sleep",
151 "sockatmark",
152 "socket",
153 "socketpair",
154 "stat",
155 "stpcpy",
156 "stpncpy",
157 "strcat",
158 "strchr",
159 "strcmp",
160 "strcpy",
161 "strcspn",
162 "strlen",
163 "strncat",
164 "strncmp",
165 "strncpy",
166 "strnlen",
167 "strpbrk",
168 "strrchr",
169 "strspn",
170 "strstr",
171 "strtok_r",
172 "symlink",
173 "symlinkat",
174 "tcdrain",
175 "tcflow",
176 "tcflush",
177 "tcgetattr",
178 "tcgetpgrp",
179 "tcsendbreak",
180 "tcsetattr",
181 "tcsetpgrp",
182 "time",
183 "timer_getoverrun",
184 "timer_gettime",
185 "timer_settime",
186 "times",
187 "umask",
188 "uname",
189 "unlink",
190 "unlinkat",
191 "utime",
192 "utimensat",
193 "utimes",
194 "wait",
195 "waitpid",
196 "wcpcpy",
197 "wcpncpy",
198 "wcscat",
199 "wcschr",
200 "wcscmp",
201 "wcscpy",
202 "wcscspn",
203 "wcslen",
204 "wcsncat",
205 "wcsncmp",
206 "wcsncpy",
207 "wcsnlen",
208 "wcspbrk",
209 "wcsrchr",
210 "wcsspn",
211 "wcsstr",
212 "wcstok",
213 "wmemchr",
214 "wmemcmp",
215 "wmemcpy",
216 "wmemmove",
217 "wmemset",
218 "write"};
219
220using namespace clang::ast_matchers;
221
222namespace clang::tidy {
223
224template <>
226 bugprone::SignalHandlerCheck::AsyncSafeFunctionSetKind> {
227 static llvm::ArrayRef<std::pair<
230 static constexpr std::pair<
232 Mapping[] = {
234 "minimal"},
236 "POSIX"},
237 };
238 return {Mapping};
239 }
240};
241
242namespace bugprone {
243
244namespace {
245
246/// Returns if a function is declared inside a system header.
247/// These functions are considered to be "standard" (system-provided) library
248/// functions.
249bool isStandardFunction(const FunctionDecl *FD) {
250 // Find a possible redeclaration in system header.
251 // FIXME: Looking at the canonical declaration is not the most exact way
252 // to do this.
253
254 // Most common case will be inclusion directly from a header.
255 // This works fine by using canonical declaration.
256 // a.c
257 // #include <sysheader.h>
258
259 // Next most common case will be extern declaration.
260 // Can't catch this with either approach.
261 // b.c
262 // extern void sysfunc(void);
263
264 // Canonical declaration is the first found declaration, so this works.
265 // c.c
266 // #include <sysheader.h>
267 // extern void sysfunc(void); // redecl won't matter
268
269 // This does not work with canonical declaration.
270 // Probably this is not a frequently used case but may happen (the first
271 // declaration can be in a non-system header for example).
272 // d.c
273 // extern void sysfunc(void); // Canonical declaration, not in system header.
274 // #include <sysheader.h>
275
276 return FD->getASTContext().getSourceManager().isInSystemHeader(
277 FD->getCanonicalDecl()->getLocation());
278}
279
280/// Check if a statement is "C++-only".
281/// This includes all statements that have a class name with "CXX" prefix
282/// and every other statement that is declared in file ExprCXX.h.
283bool isCXXOnlyStmt(const Stmt *S) {
284 StringRef Name = S->getStmtClassName();
285 if (Name.starts_with("CXX"))
286 return true;
287 // Check for all other class names in ExprCXX.h that have no 'CXX' prefix.
288 return isa<ArrayTypeTraitExpr, BuiltinBitCastExpr, CUDAKernelCallExpr,
289 CoawaitExpr, CoreturnStmt, CoroutineBodyStmt, CoroutineSuspendExpr,
290 CoyieldExpr, DependentCoawaitExpr, DependentScopeDeclRefExpr,
291 ExprWithCleanups, ExpressionTraitExpr, FunctionParmPackExpr,
292 LambdaExpr, MSDependentExistsStmt, MSPropertyRefExpr,
293 MSPropertySubscriptExpr, MaterializeTemporaryExpr, OverloadExpr,
294 PackExpansionExpr, SizeOfPackExpr, SubstNonTypeTemplateParmExpr,
295 SubstNonTypeTemplateParmPackExpr, TypeTraitExpr,
296 UserDefinedLiteral>(S);
297}
298
299/// Given a call graph node of a \p Caller function and a \p Callee that is
300/// called from \p Caller, get a \c CallExpr of the corresponding function call.
301/// It is unspecified which call is found if multiple calls exist, but the order
302/// should be deterministic (depend only on the AST).
303Expr *findCallExpr(const CallGraphNode *Caller, const CallGraphNode *Callee) {
304 auto FoundCallee = llvm::find_if(
305 Caller->callees(), [Callee](const CallGraphNode::CallRecord &Call) {
306 return Call.Callee == Callee;
307 });
308 assert(FoundCallee != Caller->end() &&
309 "Callee should be called from the caller function here.");
310 return FoundCallee->CallExpr;
311}
312
313SourceRange getSourceRangeOfStmt(const Stmt *S, ASTContext &Ctx) {
314 ParentMapContext &PM = Ctx.getParentMapContext();
315 DynTypedNode P = DynTypedNode::create(*S);
316 while (P.getSourceRange().isInvalid()) {
317 DynTypedNodeList PL = PM.getParents(P);
318 if (PL.size() != 1)
319 return {};
320 P = PL[0];
321 }
322 return P.getSourceRange();
323}
324
325} // namespace
326
327AST_MATCHER(FunctionDecl, isStandardFunction) {
328 return isStandardFunction(&Node);
329}
330
332 ClangTidyContext *Context)
333 : ClangTidyCheck(Name, Context),
334 AsyncSafeFunctionSet(Options.get("AsyncSafeFunctionSet",
335 AsyncSafeFunctionSetKind::POSIX)) {
336 if (AsyncSafeFunctionSet == AsyncSafeFunctionSetKind::Minimal) {
337 for (StringRef v : MinimalConformingFunctions)
338 ConformingFunctions.insert(v);
339 } else {
340 for (StringRef v : POSIXConformingFunctions)
341 ConformingFunctions.insert(v);
342 }
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 auto SignalFunction = functionDecl(hasAnyName("::signal", "::std::signal"),
356 parameterCountIs(2), isStandardFunction());
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 it
388 // 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 CallGraphNode *HandlerNode = CG.getNode(HandlerDecl->getCanonicalDecl());
405 assert(HandlerNode &&
406 "Handler with body should be present in the call graph.");
407 // Start from signal handler and visit every function call.
408 auto Itr = llvm::df_begin(HandlerNode), ItrE = llvm::df_end(HandlerNode);
409 while (Itr != ItrE) {
410 const auto *CallF = dyn_cast<FunctionDecl>((*Itr)->getDecl());
411 unsigned int PathL = Itr.getPathLength();
412 if (CallF) {
413 // A signal handler or a function transitively reachable from the signal
414 // handler was found to be unsafe.
415 // Generate notes for the whole call chain (including the signal handler
416 // registration).
417 const Expr *CallOrRef = (PathL > 1)
418 ? findCallExpr(Itr.getPath(PathL - 2), *Itr)
419 : HandlerExpr;
420 auto ChainReporter = [this, &Itr, HandlerExpr](bool SkipPathEnd) {
421 reportHandlerChain(Itr, HandlerExpr, SkipPathEnd);
422 };
423 // If problems were found in a function (`CallF`), skip the analysis of
424 // functions that are called from it.
425 if (checkFunction(CallF, CallOrRef, ChainReporter))
426 Itr.skipChildren();
427 else
428 ++Itr;
429 } else {
430 ++Itr;
431 }
432 }
433}
434
435bool SignalHandlerCheck::checkFunction(
436 const FunctionDecl *FD, const Expr *CallOrRef,
437 std::function<void(bool)> ChainReporter) {
438 bool FunctionIsCalled = isa<CallExpr>(CallOrRef);
439
440 if (isStandardFunction(FD)) {
441 if (!isStandardFunctionAsyncSafe(FD)) {
442 diag(CallOrRef->getBeginLoc(), "standard function %0 may not be "
443 "asynchronous-safe; "
444 "%select{using it as|calling it from}1 "
445 "a signal handler may be dangerous")
446 << FD << FunctionIsCalled << CallOrRef->getSourceRange();
447 if (ChainReporter)
448 ChainReporter(/*SkipPathEnd=*/true);
449 return true;
450 }
451 return false;
452 }
453
454 if (!FD->hasBody()) {
455 diag(CallOrRef->getBeginLoc(), "cannot verify that external function %0 is "
456 "asynchronous-safe; "
457 "%select{using it as|calling it from}1 "
458 "a signal handler may be dangerous")
459 << FD << FunctionIsCalled << CallOrRef->getSourceRange();
460 if (ChainReporter)
461 ChainReporter(/*SkipPathEnd=*/true);
462 return true;
463 }
464
465 if (getLangOpts().CPlusPlus)
466 return checkFunctionCPP14(FD, CallOrRef, ChainReporter);
467
468 return false;
469}
470
471bool SignalHandlerCheck::checkFunctionCPP14(
472 const FunctionDecl *FD, const Expr *CallOrRef,
473 std::function<void(bool)> ChainReporter) {
474 if (!FD->isExternC()) {
475 diag(CallOrRef->getBeginLoc(),
476 "functions without C linkage are not allowed as signal "
477 "handler (until C++17)");
478 if (ChainReporter)
479 ChainReporter(/*SkipPathEnd=*/true);
480 return true;
481 }
482
483 const FunctionDecl *FBody = nullptr;
484 const Stmt *BodyS = FD->getBody(FBody);
485 if (!BodyS)
486 return false;
487
488 bool StmtProblemsFound = false;
489 ASTContext &Ctx = FBody->getASTContext();
490 auto Matches =
491 match(decl(forEachDescendant(stmt().bind("stmt"))), *FBody, Ctx);
492 for (const auto &Match : Matches) {
493 const auto *FoundS = Match.getNodeAs<Stmt>("stmt");
494 if (isCXXOnlyStmt(FoundS)) {
495 SourceRange R = getSourceRangeOfStmt(FoundS, Ctx);
496 if (R.isInvalid())
497 continue;
498 diag(R.getBegin(),
499 "C++-only construct is not allowed in signal handler (until C++17)")
500 << R;
501 diag(R.getBegin(), "internally, the statement is parsed as a '%0'",
502 DiagnosticIDs::Remark)
503 << FoundS->getStmtClassName();
504 if (ChainReporter)
505 ChainReporter(/*SkipPathEnd=*/false);
506 StmtProblemsFound = true;
507 }
508 }
509
510 return StmtProblemsFound;
511}
512
513bool SignalHandlerCheck::isStandardFunctionAsyncSafe(
514 const FunctionDecl *FD) const {
515 assert(isStandardFunction(FD));
516
517 const IdentifierInfo *II = FD->getIdentifier();
518 // Unnamed functions are not explicitly allowed.
519 // C++ std operators may be unsafe and not within the
520 // "common subset of C and C++".
521 if (!II)
522 return false;
523
524 if (!FD->isInStdNamespace() && !FD->isGlobal())
525 return false;
526
527 if (ConformingFunctions.count(II->getName()))
528 return true;
529
530 return false;
531}
532
533void SignalHandlerCheck::reportHandlerChain(
534 const llvm::df_iterator<clang::CallGraphNode *> &Itr,
535 const DeclRefExpr *HandlerRef, bool SkipPathEnd) {
536 int CallLevel = Itr.getPathLength() - 2;
537 assert(CallLevel >= -1 && "Empty iterator?");
538
539 const CallGraphNode *Caller = Itr.getPath(CallLevel + 1), *Callee = nullptr;
540 while (CallLevel >= 0) {
541 Callee = Caller;
542 Caller = Itr.getPath(CallLevel);
543 const Expr *CE = findCallExpr(Caller, Callee);
544 if (SkipPathEnd)
545 SkipPathEnd = false;
546 else
547 diag(CE->getBeginLoc(), "function %0 called here from %1",
548 DiagnosticIDs::Note)
549 << cast<FunctionDecl>(Callee->getDecl())
550 << cast<FunctionDecl>(Caller->getDecl());
551 --CallLevel;
552 }
553
554 if (!SkipPathEnd)
555 diag(HandlerRef->getBeginLoc(),
556 "function %0 registered here as signal handler", DiagnosticIDs::Note)
557 << cast<FunctionDecl>(Caller->getDecl())
558 << HandlerRef->getSourceRange();
559}
560
561} // namespace bugprone
562} // namespace clang::tidy
CaptureExpr CE
llvm::SmallString< 256U > Name
::clang::DynTypedNode Node
constexpr llvm::StringLiteral MinimalConformingFunctions[]
constexpr llvm::StringLiteral POSIXConformingFunctions[]
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
const LangOptions & getLangOpts() const
Returns the language options from the context.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
SignalHandlerCheck(StringRef Name, ClangTidyContext *Context)
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override
Override this to disable registering matchers and PP callbacks if an invalid language version is bein...
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
std::vector< std::string > match(const SymbolIndex &I, const FuzzyFindRequest &Req, bool *Incomplete)
Definition: TestIndex.cpp:139
AST_MATCHER(clang::VarDecl, hasConstantDeclaration)
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...