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 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 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 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.contains(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
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::ProBoundsAvoidUncheckedContainerAccess 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...