blob: 4ee5159b43d5747b030c3180910205b0c426079d [file] [log] [blame]
George Karpenkov44a3b7c2018-03-12 18:27:36 +00001//===- GCDAntipatternChecker.cpp ---------------------------------*- C++ -*-==//
George Karpenkov436d5cc2018-03-05 22:03:32 +00002//
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//
George Karpenkov44a3b7c2018-03-12 18:27:36 +000010// This file defines GCDAntipatternChecker which checks against a common
George Karpenkov436d5cc2018-03-05 22:03:32 +000011// antipattern when synchronous API is emulated from asynchronous callbacks
George Karpenkov44a3b7c2018-03-12 18:27:36 +000012// using a semaphore:
George Karpenkov436d5cc2018-03-05 22:03:32 +000013//
George Karpenkov44a3b7c2018-03-12 18:27:36 +000014// dispatch_semaphore_t sema = dispatch_semaphore_create(0);
15//
George Karpenkov436d5cc2018-03-05 22:03:32 +000016// AnyCFunctionCall(^{
17// // code…
George Karpenkov44a3b7c2018-03-12 18:27:36 +000018// dispatch_semaphore_signal(sema);
George Karpenkov436d5cc2018-03-05 22:03:32 +000019// })
George Karpenkov44a3b7c2018-03-12 18:27:36 +000020// dispatch_semaphore_wait(sema, *)
George Karpenkov436d5cc2018-03-05 22:03:32 +000021//
22// Such code is a common performance problem, due to inability of GCD to
George Karpenkov44a3b7c2018-03-12 18:27:36 +000023// properly handle QoS when a combination of queues and semaphores is used.
George Karpenkov436d5cc2018-03-05 22:03:32 +000024// 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
George Karpenkov436d5cc2018-03-05 22:03:32 +000040using namespace clang;
41using namespace ento;
42using namespace ast_matchers;
43
44namespace {
45
George Karpenkov628920b2018-03-23 00:16:02 +000046// ID of a node at which the diagnostic would be emitted.
47const char *WarnAtNode = "waitcall";
George Karpenkov436d5cc2018-03-05 22:03:32 +000048
George Karpenkov44a3b7c2018-03-12 18:27:36 +000049class GCDAntipatternChecker : public Checker<check::ASTCodeBody> {
George Karpenkov436d5cc2018-03-05 22:03:32 +000050public:
51 void checkASTCodeBody(const Decl *D,
52 AnalysisManager &AM,
53 BugReporter &BR) const;
54};
55
George Karpenkov436d5cc2018-03-05 22:03:32 +000056auto 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
George Karpenkov628920b2018-03-23 00:16:02 +000072/// 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) {
George Karpenkov15e814f2018-03-06 00:18:21 +000077 if (const auto* ND = dyn_cast<NamedDecl>(D)) {
78 std::string DeclName = ND->getNameAsString();
79 if (StringRef(DeclName).startswith("test"))
George Karpenkov628920b2018-03-23 00:16:02 +000080 return true;
George Karpenkov15e814f2018-03-06 00:18:21 +000081 }
George Karpenkov44a3b7c2018-03-12 18:27:36 +000082 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"))
George Karpenkov628920b2018-03-23 00:16:02 +000087 return true;
George Karpenkov44a3b7c2018-03-12 18:27:36 +000088 }
89 }
George Karpenkov628920b2018-03-23 00:16:02 +000090 return false;
91}
92
93static auto findGCDAntiPatternWithSemaphore() -> decltype(compoundStmt()) {
George Karpenkov436d5cc2018-03-05 22:03:32 +000094
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
George Karpenkov4cbeeb12018-03-07 02:54:01 +0000104 auto HasBlockArgumentM = hasAnyArgument(hasType(
George Karpenkov436d5cc2018-03-05 22:03:32 +0000105 hasCanonicalType(blockPointerType())
George Karpenkov4cbeeb12018-03-07 02:54:01 +0000106 ));
George Karpenkov436d5cc2018-03-05 22:03:32 +0000107
George Karpenkov460675e2018-03-13 17:27:01 +0000108 auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr(
George Karpenkov436d5cc2018-03-05 22:03:32 +0000109 allOf(
110 callsName("dispatch_semaphore_signal"),
111 equalsBoundArgDecl(0, SemaphoreBinding)
George Karpenkov460675e2018-03-13 17:27:01 +0000112 )))));
George Karpenkov436d5cc2018-03-05 22:03:32 +0000113
George Karpenkov4cbeeb12018-03-07 02:54:01 +0000114 auto HasBlockAndCallsSignalM = allOf(HasBlockArgumentM, ArgCallsSignalM);
115
George Karpenkov628920b2018-03-23 00:16:02 +0000116 auto HasBlockCallingSignalM =
George Karpenkov4cbeeb12018-03-07 02:54:01 +0000117 forEachDescendant(
118 stmt(anyOf(
119 callExpr(HasBlockAndCallsSignalM),
120 objcMessageExpr(HasBlockAndCallsSignalM)
121 )));
122
George Karpenkov628920b2018-03-23 00:16:02 +0000123 auto SemaphoreWaitM = forEachDescendant(
124 callExpr(
125 allOf(
126 callsName("dispatch_semaphore_wait"),
127 equalsBoundArgDecl(0, SemaphoreBinding)
128 )
129 ).bind(WarnAtNode));
George Karpenkov436d5cc2018-03-05 22:03:32 +0000130
George Karpenkov628920b2018-03-23 00:16:02 +0000131 return compoundStmt(
132 SemaphoreBindingM, HasBlockCallingSignalM, SemaphoreWaitM);
George Karpenkov436d5cc2018-03-05 22:03:32 +0000133}
134
George Karpenkov628920b2018-03-23 00:16:02 +0000135static 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
180static 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);
George Karpenkov436d5cc2018-03-05 22:03:32 +0000186 assert(SW);
George Karpenkov628920b2018-03-23 00:16:02 +0000187
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
George Karpenkov436d5cc2018-03-05 22:03:32 +0000194 BR.EmitBasicReport(
George Karpenkov628920b2018-03-23 00:16:02 +0000195 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
204void 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);
George Karpenkov436d5cc2018-03-05 22:03:32 +0000221}
222
223}
224
George Karpenkov44a3b7c2018-03-12 18:27:36 +0000225void ento::registerGCDAntipattern(CheckerManager &Mgr) {
226 Mgr.registerChecker<GCDAntipatternChecker>();
George Karpenkov436d5cc2018-03-05 22:03:32 +0000227}