clang-tools 22.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
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
26// date.
27// clang-format off
28constexpr 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
224using namespace clang::ast_matchers;
225
226namespace clang::tidy {
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 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 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 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 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(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 of
425 // functions that are called from it.
426 if (checkFunction(CallF, CallOrRef, ChainReporter))
427 Itr.skipChildren();
428 else
429 ++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 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.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 else
548 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 bugprone
563} // namespace clang::tidy
constexpr llvm::StringLiteral MinimalConformingFunctions[]
constexpr llvm::StringLiteral POSIXConformingFunctions[]
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
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...