]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/llvm/tools/clang/lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp
Merge clang 7.0.1 and several follow-up changes
[FreeBSD/FreeBSD.git] / contrib / llvm / tools / clang / lib / StaticAnalyzer / Checkers / GCDAntipatternChecker.cpp
1 //===- GCDAntipatternChecker.cpp ---------------------------------*- C++ -*-==//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 //
10 // This file defines GCDAntipatternChecker which checks against a common
11 // antipattern when synchronous API is emulated from asynchronous callbacks
12 // using a semaphore:
13 //
14 //   dispatch_semaphore_t sema = dispatch_semaphore_create(0);
15 //
16 //   AnyCFunctionCall(^{
17 //     // codeā€¦
18 //     dispatch_semaphore_signal(sema);
19 //   })
20 //   dispatch_semaphore_wait(sema, *)
21 //
22 // Such code is a common performance problem, due to inability of GCD to
23 // properly handle QoS when a combination of queues and semaphores is used.
24 // Good code would either use asynchronous API (when available), or perform
25 // the necessary action in asynchronous callback.
26 //
27 // Currently, the check is performed using a simple heuristical AST pattern
28 // matching.
29 //
30 //===----------------------------------------------------------------------===//
31
32 #include "ClangSACheckers.h"
33 #include "clang/ASTMatchers/ASTMatchFinder.h"
34 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
35 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
36 #include "clang/StaticAnalyzer/Core/Checker.h"
37 #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
38 #include "llvm/Support/Debug.h"
39
40 using namespace clang;
41 using namespace ento;
42 using namespace ast_matchers;
43
44 namespace {
45
46 // ID of a node at which the diagnostic would be emitted.
47 const char *WarnAtNode = "waitcall";
48
49 class GCDAntipatternChecker : public Checker<check::ASTCodeBody> {
50 public:
51   void checkASTCodeBody(const Decl *D,
52                         AnalysisManager &AM,
53                         BugReporter &BR) const;
54 };
55
56 auto callsName(const char *FunctionName)
57     -> decltype(callee(functionDecl())) {
58   return callee(functionDecl(hasName(FunctionName)));
59 }
60
61 auto equalsBoundArgDecl(int ArgIdx, const char *DeclName)
62     -> decltype(hasArgument(0, expr())) {
63   return hasArgument(ArgIdx, ignoringParenCasts(declRefExpr(
64                                  to(varDecl(equalsBoundNode(DeclName))))));
65 }
66
67 auto bindAssignmentToDecl(const char *DeclName) -> decltype(hasLHS(expr())) {
68   return hasLHS(ignoringParenImpCasts(
69                          declRefExpr(to(varDecl().bind(DeclName)))));
70 }
71
72 /// The pattern is very common in tests, and it is OK to use it there.
73 /// We have to heuristics for detecting tests: method name starts with "test"
74 /// (used in XCTest), and a class name contains "mock" or "test" (used in
75 /// helpers which are not tests themselves, but used exclusively in tests).
76 static bool isTest(const Decl *D) {
77   if (const auto* ND = dyn_cast<NamedDecl>(D)) {
78     std::string DeclName = ND->getNameAsString();
79     if (StringRef(DeclName).startswith("test"))
80       return true;
81   }
82   if (const auto *OD = dyn_cast<ObjCMethodDecl>(D)) {
83     if (const auto *CD = dyn_cast<ObjCContainerDecl>(OD->getParent())) {
84       std::string ContainerName = CD->getNameAsString();
85       StringRef CN(ContainerName);
86       if (CN.contains_lower("test") || CN.contains_lower("mock"))
87         return true;
88     }
89   }
90   return false;
91 }
92
93 static auto findGCDAntiPatternWithSemaphore() -> decltype(compoundStmt()) {
94
95   const char *SemaphoreBinding = "semaphore_name";
96   auto SemaphoreCreateM = callExpr(allOf(
97       callsName("dispatch_semaphore_create"),
98       hasArgument(0, ignoringParenCasts(integerLiteral(equals(0))))));
99
100   auto SemaphoreBindingM = anyOf(
101       forEachDescendant(
102           varDecl(hasDescendant(SemaphoreCreateM)).bind(SemaphoreBinding)),
103       forEachDescendant(binaryOperator(bindAssignmentToDecl(SemaphoreBinding),
104                      hasRHS(SemaphoreCreateM))));
105
106   auto HasBlockArgumentM = hasAnyArgument(hasType(
107             hasCanonicalType(blockPointerType())
108             ));
109
110   auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr(
111           allOf(
112               callsName("dispatch_semaphore_signal"),
113               equalsBoundArgDecl(0, SemaphoreBinding)
114               )))));
115
116   auto HasBlockAndCallsSignalM = allOf(HasBlockArgumentM, ArgCallsSignalM);
117
118   auto HasBlockCallingSignalM =
119     forEachDescendant(
120       stmt(anyOf(
121         callExpr(HasBlockAndCallsSignalM),
122         objcMessageExpr(HasBlockAndCallsSignalM)
123            )));
124
125   auto SemaphoreWaitM = forEachDescendant(
126     callExpr(
127       allOf(
128         callsName("dispatch_semaphore_wait"),
129         equalsBoundArgDecl(0, SemaphoreBinding)
130       )
131     ).bind(WarnAtNode));
132
133   return compoundStmt(
134       SemaphoreBindingM, HasBlockCallingSignalM, SemaphoreWaitM);
135 }
136
137 static auto findGCDAntiPatternWithGroup() -> decltype(compoundStmt()) {
138
139   const char *GroupBinding = "group_name";
140   auto DispatchGroupCreateM = callExpr(callsName("dispatch_group_create"));
141
142   auto GroupBindingM = anyOf(
143       forEachDescendant(
144           varDecl(hasDescendant(DispatchGroupCreateM)).bind(GroupBinding)),
145       forEachDescendant(binaryOperator(bindAssignmentToDecl(GroupBinding),
146                      hasRHS(DispatchGroupCreateM))));
147
148   auto GroupEnterM = forEachDescendant(
149       stmt(callExpr(allOf(callsName("dispatch_group_enter"),
150                           equalsBoundArgDecl(0, GroupBinding)))));
151
152   auto HasBlockArgumentM = hasAnyArgument(hasType(
153             hasCanonicalType(blockPointerType())
154             ));
155
156   auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr(
157           allOf(
158               callsName("dispatch_group_leave"),
159               equalsBoundArgDecl(0, GroupBinding)
160               )))));
161
162   auto HasBlockAndCallsLeaveM = allOf(HasBlockArgumentM, ArgCallsSignalM);
163
164   auto AcceptsBlockM =
165     forEachDescendant(
166       stmt(anyOf(
167         callExpr(HasBlockAndCallsLeaveM),
168         objcMessageExpr(HasBlockAndCallsLeaveM)
169            )));
170
171   auto GroupWaitM = forEachDescendant(
172     callExpr(
173       allOf(
174         callsName("dispatch_group_wait"),
175         equalsBoundArgDecl(0, GroupBinding)
176       )
177     ).bind(WarnAtNode));
178
179   return compoundStmt(GroupBindingM, GroupEnterM, AcceptsBlockM, GroupWaitM);
180 }
181
182 static void emitDiagnostics(const BoundNodes &Nodes,
183                             const char* Type,
184                             BugReporter &BR,
185                             AnalysisDeclContext *ADC,
186                             const GCDAntipatternChecker *Checker) {
187   const auto *SW = Nodes.getNodeAs<CallExpr>(WarnAtNode);
188   assert(SW);
189
190   std::string Diagnostics;
191   llvm::raw_string_ostream OS(Diagnostics);
192   OS << "Waiting on a callback using a " << Type << " creates useless threads "
193      << "and is subject to priority inversion; consider "
194      << "using a synchronous API or changing the caller to be asynchronous";
195
196   BR.EmitBasicReport(
197     ADC->getDecl(),
198     Checker,
199     /*Name=*/"GCD performance anti-pattern",
200     /*Category=*/"Performance",
201     OS.str(),
202     PathDiagnosticLocation::createBegin(SW, BR.getSourceManager(), ADC),
203     SW->getSourceRange());
204 }
205
206 void GCDAntipatternChecker::checkASTCodeBody(const Decl *D,
207                                              AnalysisManager &AM,
208                                              BugReporter &BR) const {
209   if (isTest(D))
210     return;
211
212   AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);
213
214   auto SemaphoreMatcherM = findGCDAntiPatternWithSemaphore();
215   auto Matches = match(SemaphoreMatcherM, *D->getBody(), AM.getASTContext());
216   for (BoundNodes Match : Matches)
217     emitDiagnostics(Match, "semaphore", BR, ADC, this);
218
219   auto GroupMatcherM = findGCDAntiPatternWithGroup();
220   Matches = match(GroupMatcherM, *D->getBody(), AM.getASTContext());
221   for (BoundNodes Match : Matches)
222     emitDiagnostics(Match, "group", BR, ADC, this);
223 }
224
225 }
226
227 void ento::registerGCDAntipattern(CheckerManager &Mgr) {
228   Mgr.registerChecker<GCDAntipatternChecker>();
229 }