• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/AST/ASTContext.h"
11 #include "clang/AST/RecursiveASTVisitor.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "clang/Analysis/CallGraph.h"
14 #include "llvm/ADT/DenseSet.h"
15 #include "llvm/ADT/STLExtras.h"
16 #include "llvm/ADT/SmallVector.h"
17 #include <iterator>
18 #include <queue>
19 
20 using namespace clang::ast_matchers;
21 
22 namespace clang {
23 namespace tidy {
24 namespace bugprone {
25 
isSystemCall(const FunctionDecl * FD)26 static bool isSystemCall(const FunctionDecl *FD) {
27   // Find a possible redeclaration in system header.
28   // FIXME: Looking at the canonical declaration is not the most exact way
29   // to do this.
30 
31   // Most common case will be inclusion directly from a header.
32   // This works fine by using canonical declaration.
33   // a.c
34   // #include <sysheader.h>
35 
36   // Next most common case will be extern declaration.
37   // Can't catch this with either approach.
38   // b.c
39   // extern void sysfunc(void);
40 
41   // Canonical declaration is the first found declaration, so this works.
42   // c.c
43   // #include <sysheader.h>
44   // extern void sysfunc(void); // redecl won't matter
45 
46   // This does not work with canonical declaration.
47   // Probably this is not a frequently used case but may happen (the first
48   // declaration can be in a non-system header for example).
49   // d.c
50   // extern void sysfunc(void); // Canonical declaration, not in system header.
51   // #include <sysheader.h>
52 
53   return FD->getASTContext().getSourceManager().isInSystemHeader(
54       FD->getCanonicalDecl()->getLocation());
55 }
56 
AST_MATCHER(FunctionDecl,isSystemCall)57 AST_MATCHER(FunctionDecl, isSystemCall) { return isSystemCall(&Node); }
58 
59 // This is the  minimal set of safe functions.
60 // FIXME: Add checker option to allow a POSIX compliant extended set.
61 llvm::StringSet<> SignalHandlerCheck::StrictConformingFunctions{
62     "signal", "abort", "_Exit", "quick_exit"};
63 
SignalHandlerCheck(StringRef Name,ClangTidyContext * Context)64 SignalHandlerCheck::SignalHandlerCheck(StringRef Name,
65                                        ClangTidyContext *Context)
66     : ClangTidyCheck(Name, Context) {}
67 
isLanguageVersionSupported(const LangOptions & LangOpts) const68 bool SignalHandlerCheck::isLanguageVersionSupported(
69     const LangOptions &LangOpts) const {
70   // FIXME: Make the checker useful on C++ code.
71   if (LangOpts.CPlusPlus)
72     return false;
73 
74   return true;
75 }
76 
registerMatchers(MatchFinder * Finder)77 void SignalHandlerCheck::registerMatchers(MatchFinder *Finder) {
78   auto SignalFunction = functionDecl(hasAnyName("::signal", "::std::signal"),
79                                      parameterCountIs(2), isSystemCall());
80   auto HandlerExpr =
81       declRefExpr(hasDeclaration(functionDecl().bind("handler_decl")),
82                   unless(isExpandedFromMacro("SIG_IGN")),
83                   unless(isExpandedFromMacro("SIG_DFL")))
84           .bind("handler_expr");
85   Finder->addMatcher(
86       callExpr(callee(SignalFunction), hasArgument(1, HandlerExpr))
87           .bind("register_call"),
88       this);
89 }
90 
check(const MatchFinder::MatchResult & Result)91 void SignalHandlerCheck::check(const MatchFinder::MatchResult &Result) {
92   const auto *SignalCall = Result.Nodes.getNodeAs<CallExpr>("register_call");
93   const auto *HandlerDecl =
94       Result.Nodes.getNodeAs<FunctionDecl>("handler_decl");
95   const auto *HandlerExpr = Result.Nodes.getNodeAs<DeclRefExpr>("handler_expr");
96 
97   // Visit each function encountered in the callgraph only once.
98   llvm::DenseSet<const FunctionDecl *> SeenFunctions;
99 
100   // The worklist of the callgraph visitation algorithm.
101   std::deque<const CallExpr *> CalledFunctions;
102 
103   auto ProcessFunction = [&](const FunctionDecl *F, const Expr *CallOrRef) {
104     // Ensure that canonical declaration is used.
105     F = F->getCanonicalDecl();
106 
107     // Do not visit function if already encountered.
108     if (!SeenFunctions.insert(F).second)
109       return true;
110 
111     // Check if the call is allowed.
112     // Non-system calls are not considered.
113     if (isSystemCall(F)) {
114       if (isSystemCallAllowed(F))
115         return true;
116 
117       reportBug(F, CallOrRef, SignalCall, HandlerDecl);
118 
119       return false;
120     }
121 
122     // Get the body of the encountered non-system call function.
123     const FunctionDecl *FBody;
124     if (!F->hasBody(FBody)) {
125       reportBug(F, CallOrRef, SignalCall, HandlerDecl);
126       return false;
127     }
128 
129     // Collect all called functions.
130     auto Matches = match(decl(forEachDescendant(callExpr().bind("call"))),
131                          *FBody, FBody->getASTContext());
132     for (const auto &Match : Matches) {
133       const auto *CE = Match.getNodeAs<CallExpr>("call");
134       if (isa<FunctionDecl>(CE->getCalleeDecl()))
135         CalledFunctions.push_back(CE);
136     }
137 
138     return true;
139   };
140 
141   if (!ProcessFunction(HandlerDecl, HandlerExpr))
142     return;
143 
144   // Visit the definition of every function referenced by the handler function.
145   // Check for allowed function calls.
146   while (!CalledFunctions.empty()) {
147     const CallExpr *FunctionCall = CalledFunctions.front();
148     CalledFunctions.pop_front();
149     // At insertion we have already ensured that only function calls are there.
150     const auto *F = cast<FunctionDecl>(FunctionCall->getCalleeDecl());
151 
152     if (!ProcessFunction(F, FunctionCall))
153       break;
154   }
155 }
156 
isSystemCallAllowed(const FunctionDecl * FD) const157 bool SignalHandlerCheck::isSystemCallAllowed(const FunctionDecl *FD) const {
158   const IdentifierInfo *II = FD->getIdentifier();
159   // Unnamed functions are not explicitly allowed.
160   if (!II)
161     return false;
162 
163   // FIXME: Improve for C++ (check for namespace).
164   if (StrictConformingFunctions.count(II->getName()))
165     return true;
166 
167   return false;
168 }
169 
reportBug(const FunctionDecl * CalledFunction,const Expr * CallOrRef,const CallExpr * SignalCall,const FunctionDecl * HandlerDecl)170 void SignalHandlerCheck::reportBug(const FunctionDecl *CalledFunction,
171                                    const Expr *CallOrRef,
172                                    const CallExpr *SignalCall,
173                                    const FunctionDecl *HandlerDecl) {
174   diag(CallOrRef->getBeginLoc(),
175        "%0 may not be asynchronous-safe; "
176        "calling it from a signal handler may be dangerous")
177       << CalledFunction;
178   diag(SignalCall->getSourceRange().getBegin(),
179        "signal handler registered here", DiagnosticIDs::Note);
180   diag(HandlerDecl->getBeginLoc(), "handler function declared here",
181        DiagnosticIDs::Note);
182 }
183 
184 } // namespace bugprone
185 } // namespace tidy
186 } // namespace clang
187