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(callsName("dispatch_semaphore_create"));
97 
98   auto SemaphoreBindingM = anyOf(
99       forEachDescendant(
100           varDecl(hasDescendant(SemaphoreCreateM)).bind(SemaphoreBinding)),
101       forEachDescendant(binaryOperator(bindAssignmentToDecl(SemaphoreBinding),
102                      hasRHS(SemaphoreCreateM))));
103 
104   auto HasBlockArgumentM = hasAnyArgument(hasType(
105             hasCanonicalType(blockPointerType())
106             ));
107 
108   auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr(
109           allOf(
110               callsName("dispatch_semaphore_signal"),
111               equalsBoundArgDecl(0, SemaphoreBinding)
112               )))));
113 
114   auto HasBlockAndCallsSignalM = allOf(HasBlockArgumentM, ArgCallsSignalM);
115 
116   auto HasBlockCallingSignalM =
117     forEachDescendant(
118       stmt(anyOf(
119         callExpr(HasBlockAndCallsSignalM),
120         objcMessageExpr(HasBlockAndCallsSignalM)
121            )));
122 
123   auto SemaphoreWaitM = forEachDescendant(
124     callExpr(
125       allOf(
126         callsName("dispatch_semaphore_wait"),
127         equalsBoundArgDecl(0, SemaphoreBinding)
128       )
129     ).bind(WarnAtNode));
130 
131   return compoundStmt(
132       SemaphoreBindingM, HasBlockCallingSignalM, SemaphoreWaitM);
133 }
134 
135 static auto findGCDAntiPatternWithGroup() -> decltype(compoundStmt()) {
136 
137   const char *GroupBinding = "group_name";
138   auto DispatchGroupCreateM = callExpr(callsName("dispatch_group_create"));
139 
140   auto GroupBindingM = anyOf(
141       forEachDescendant(
142           varDecl(hasDescendant(DispatchGroupCreateM)).bind(GroupBinding)),
143       forEachDescendant(binaryOperator(bindAssignmentToDecl(GroupBinding),
144                      hasRHS(DispatchGroupCreateM))));
145 
146   auto GroupEnterM = forEachDescendant(
147       stmt(callExpr(allOf(callsName("dispatch_group_enter"),
148                           equalsBoundArgDecl(0, GroupBinding)))));
149 
150   auto HasBlockArgumentM = hasAnyArgument(hasType(
151             hasCanonicalType(blockPointerType())
152             ));
153 
154   auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr(
155           allOf(
156               callsName("dispatch_group_leave"),
157               equalsBoundArgDecl(0, GroupBinding)
158               )))));
159 
160   auto HasBlockAndCallsLeaveM = allOf(HasBlockArgumentM, ArgCallsSignalM);
161 
162   auto AcceptsBlockM =
163     forEachDescendant(
164       stmt(anyOf(
165         callExpr(HasBlockAndCallsLeaveM),
166         objcMessageExpr(HasBlockAndCallsLeaveM)
167            )));
168 
169   auto GroupWaitM = forEachDescendant(
170     callExpr(
171       allOf(
172         callsName("dispatch_group_wait"),
173         equalsBoundArgDecl(0, GroupBinding)
174       )
175     ).bind(WarnAtNode));
176 
177   return compoundStmt(GroupBindingM, GroupEnterM, AcceptsBlockM, GroupWaitM);
178 }
179 
180 static void emitDiagnostics(const BoundNodes &Nodes,
181                             const char* Type,
182                             BugReporter &BR,
183                             AnalysisDeclContext *ADC,
184                             const GCDAntipatternChecker *Checker) {
185   const auto *SW = Nodes.getNodeAs<CallExpr>(WarnAtNode);
186   assert(SW);
187 
188   std::string Diagnostics;
189   llvm::raw_string_ostream OS(Diagnostics);
190   OS << "Waiting on a " << Type << " with Grand Central Dispatch creates "
191      << "useless threads and is subject to priority inversion; consider "
192      << "using a synchronous API or changing the caller to be asynchronous";
193 
194   BR.EmitBasicReport(
195     ADC->getDecl(),
196     Checker,
197     /*Name=*/"GCD performance anti-pattern",
198     /*Category=*/"Performance",
199     OS.str(),
200     PathDiagnosticLocation::createBegin(SW, BR.getSourceManager(), ADC),
201     SW->getSourceRange());
202 }
203 
204 void GCDAntipatternChecker::checkASTCodeBody(const Decl *D,
205                                              AnalysisManager &AM,
206                                              BugReporter &BR) const {
207   if (isTest(D))
208     return;
209 
210   AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);
211 
212   auto SemaphoreMatcherM = findGCDAntiPatternWithSemaphore();
213   auto Matches = match(SemaphoreMatcherM, *D->getBody(), AM.getASTContext());
214   for (BoundNodes Match : Matches)
215     emitDiagnostics(Match, "semaphore", BR, ADC, this);
216 
217   auto GroupMatcherM = findGCDAntiPatternWithGroup();
218   Matches = match(GroupMatcherM, *D->getBody(), AM.getASTContext());
219   for (BoundNodes Match : Matches)
220     emitDiagnostics(Match, "group", BR, ADC, this);
221 }
222 
223 }
224 
225 void ento::registerGCDAntipattern(CheckerManager &Mgr) {
226   Mgr.registerChecker<GCDAntipatternChecker>();
227 }
228