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