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
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 <>
229struct 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
246namespace bugprone {
247
248namespace {
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.
253bool 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.
287bool 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).
307Expr *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
317SourceRange 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
329AST_MATCHER(FunctionDecl, isStandardFunction) {
330 return isStandardFunction(FD: &Node);
331}
332
333} // namespace
334
335SignalHandlerCheck::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
346void SignalHandlerCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
347 Options.store(Options&: Opts, LocalName: "AsyncSafeFunctionSet", Value: AsyncSafeFunctionSet);
348}
349
350bool SignalHandlerCheck::isLanguageVersionSupported(
351 const LangOptions &LangOpts) const {
352 return !LangOpts.CPlusPlus17;
353}
354
355void 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
371void 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
436bool 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
472bool 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
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.count(Key: II->getName()))
529 return true;
530
531 return false;
532}
533
534void 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

Provided by KDAB

Privacy Policy
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more

source code of clang-tools-extra/clang-tidy/bugprone/SignalHandlerCheck.cpp