Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp
35269 views
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
decltype(auto) callsName(const char *FunctionName) {
56
return callee(functionDecl(hasName(FunctionName)));
57
}
58
59
decltype(auto) equalsBoundArgDecl(int ArgIdx, const char *DeclName) {
60
return hasArgument(ArgIdx, ignoringParenCasts(declRefExpr(
61
to(varDecl(equalsBoundNode(DeclName))))));
62
}
63
64
decltype(auto) bindAssignmentToDecl(const char *DeclName) {
65
return hasLHS(ignoringParenImpCasts(
66
declRefExpr(to(varDecl().bind(DeclName)))));
67
}
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
static bool isTest(const Decl *D) {
74
if (const auto* ND = dyn_cast<NamedDecl>(D)) {
75
std::string DeclName = ND->getNameAsString();
76
if (StringRef(DeclName).starts_with("test"))
77
return true;
78
}
79
if (const auto *OD = dyn_cast<ObjCMethodDecl>(D)) {
80
if (const auto *CD = dyn_cast<ObjCContainerDecl>(OD->getParent())) {
81
std::string ContainerName = CD->getNameAsString();
82
StringRef CN(ContainerName);
83
if (CN.contains_insensitive("test") || CN.contains_insensitive("mock"))
84
return true;
85
}
86
}
87
return false;
88
}
89
90
static auto findGCDAntiPatternWithSemaphore() -> decltype(compoundStmt()) {
91
92
const char *SemaphoreBinding = "semaphore_name";
93
auto SemaphoreCreateM = callExpr(allOf(
94
callsName("dispatch_semaphore_create"),
95
hasArgument(0, ignoringParenCasts(integerLiteral(equals(0))))));
96
97
auto SemaphoreBindingM = anyOf(
98
forEachDescendant(
99
varDecl(hasDescendant(SemaphoreCreateM)).bind(SemaphoreBinding)),
100
forEachDescendant(binaryOperator(bindAssignmentToDecl(SemaphoreBinding),
101
hasRHS(SemaphoreCreateM))));
102
103
auto HasBlockArgumentM = hasAnyArgument(hasType(
104
hasCanonicalType(blockPointerType())
105
));
106
107
auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr(
108
allOf(
109
callsName("dispatch_semaphore_signal"),
110
equalsBoundArgDecl(0, SemaphoreBinding)
111
)))));
112
113
auto HasBlockAndCallsSignalM = allOf(HasBlockArgumentM, ArgCallsSignalM);
114
115
auto HasBlockCallingSignalM =
116
forEachDescendant(
117
stmt(anyOf(
118
callExpr(HasBlockAndCallsSignalM),
119
objcMessageExpr(HasBlockAndCallsSignalM)
120
)));
121
122
auto SemaphoreWaitM = forEachDescendant(
123
callExpr(
124
allOf(
125
callsName("dispatch_semaphore_wait"),
126
equalsBoundArgDecl(0, SemaphoreBinding)
127
)
128
).bind(WarnAtNode));
129
130
return compoundStmt(
131
SemaphoreBindingM, HasBlockCallingSignalM, SemaphoreWaitM);
132
}
133
134
static auto findGCDAntiPatternWithGroup() -> decltype(compoundStmt()) {
135
136
const char *GroupBinding = "group_name";
137
auto DispatchGroupCreateM = callExpr(callsName("dispatch_group_create"));
138
139
auto GroupBindingM = anyOf(
140
forEachDescendant(
141
varDecl(hasDescendant(DispatchGroupCreateM)).bind(GroupBinding)),
142
forEachDescendant(binaryOperator(bindAssignmentToDecl(GroupBinding),
143
hasRHS(DispatchGroupCreateM))));
144
145
auto GroupEnterM = forEachDescendant(
146
stmt(callExpr(allOf(callsName("dispatch_group_enter"),
147
equalsBoundArgDecl(0, GroupBinding)))));
148
149
auto HasBlockArgumentM = hasAnyArgument(hasType(
150
hasCanonicalType(blockPointerType())
151
));
152
153
auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr(
154
allOf(
155
callsName("dispatch_group_leave"),
156
equalsBoundArgDecl(0, GroupBinding)
157
)))));
158
159
auto HasBlockAndCallsLeaveM = allOf(HasBlockArgumentM, ArgCallsSignalM);
160
161
auto AcceptsBlockM =
162
forEachDescendant(
163
stmt(anyOf(
164
callExpr(HasBlockAndCallsLeaveM),
165
objcMessageExpr(HasBlockAndCallsLeaveM)
166
)));
167
168
auto GroupWaitM = forEachDescendant(
169
callExpr(
170
allOf(
171
callsName("dispatch_group_wait"),
172
equalsBoundArgDecl(0, GroupBinding)
173
)
174
).bind(WarnAtNode));
175
176
return compoundStmt(GroupBindingM, GroupEnterM, AcceptsBlockM, GroupWaitM);
177
}
178
179
static void emitDiagnostics(const BoundNodes &Nodes,
180
const char* Type,
181
BugReporter &BR,
182
AnalysisDeclContext *ADC,
183
const GCDAntipatternChecker *Checker) {
184
const auto *SW = Nodes.getNodeAs<CallExpr>(WarnAtNode);
185
assert(SW);
186
187
std::string Diagnostics;
188
llvm::raw_string_ostream OS(Diagnostics);
189
OS << "Waiting on a callback using a " << Type << " creates useless threads "
190
<< "and is subject to priority inversion; consider "
191
<< "using a synchronous API or changing the caller to be asynchronous";
192
193
BR.EmitBasicReport(
194
ADC->getDecl(),
195
Checker,
196
/*Name=*/"GCD performance anti-pattern",
197
/*BugCategory=*/"Performance",
198
OS.str(),
199
PathDiagnosticLocation::createBegin(SW, BR.getSourceManager(), ADC),
200
SW->getSourceRange());
201
}
202
203
void GCDAntipatternChecker::checkASTCodeBody(const Decl *D,
204
AnalysisManager &AM,
205
BugReporter &BR) const {
206
if (isTest(D))
207
return;
208
209
AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);
210
211
auto SemaphoreMatcherM = findGCDAntiPatternWithSemaphore();
212
auto Matches = match(SemaphoreMatcherM, *D->getBody(), AM.getASTContext());
213
for (BoundNodes Match : Matches)
214
emitDiagnostics(Match, "semaphore", BR, ADC, this);
215
216
auto GroupMatcherM = findGCDAntiPatternWithGroup();
217
Matches = match(GroupMatcherM, *D->getBody(), AM.getASTContext());
218
for (BoundNodes Match : Matches)
219
emitDiagnostics(Match, "group", BR, ADC, this);
220
}
221
222
} // end of anonymous namespace
223
224
void ento::registerGCDAntipattern(CheckerManager &Mgr) {
225
Mgr.registerChecker<GCDAntipatternChecker>();
226
}
227
228
bool ento::shouldRegisterGCDAntipattern(const CheckerManager &mgr) {
229
return true;
230
}
231
232