Deleted Added
sdiff udiff text old ( 344779 ) new ( 353358 )
full compact
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 "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.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
40using namespace clang;
41using namespace ento;
42using namespace ast_matchers;
43
44namespace {
45
46// ID of a node at which the diagnostic would be emitted.
47const char *WarnAtNode = "waitcall";
48
49class GCDAntipatternChecker : public Checker<check::ASTCodeBody> {
50public:
51 void checkASTCodeBody(const Decl *D,
52 AnalysisManager &AM,
53 BugReporter &BR) const;
54};
55
56auto callsName(const char *FunctionName)
57 -> decltype(callee(functionDecl())) {
58 return callee(functionDecl(hasName(FunctionName)));
59}
60
61auto equalsBoundArgDecl(int ArgIdx, const char *DeclName)
62 -> decltype(hasArgument(0, expr())) {
63 return hasArgument(ArgIdx, ignoringParenCasts(declRefExpr(
64 to(varDecl(equalsBoundNode(DeclName))))));
65}
66
67auto 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).
76static 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
93static 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
137static 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
182static 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
206void 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
227void ento::registerGCDAntipattern(CheckerManager &Mgr) {
228 Mgr.registerChecker<GCDAntipatternChecker>();
229}