1 //===- GCDAntipatternChecker.cpp ---------------------------------*- C++ -*-==//
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
7 //===----------------------------------------------------------------------===//
9 // This file defines GCDAntipatternChecker which checks against a common
10 // antipattern when synchronous API is emulated from asynchronous callbacks
13 // dispatch_semaphore_t sema = dispatch_semaphore_create(0);
15 // AnyCFunctionCall(^{
17 // dispatch_semaphore_signal(sema);
19 // dispatch_semaphore_wait(sema, *)
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.
26 // Currently, the check is performed using a simple heuristical AST pattern
29 //===----------------------------------------------------------------------===//
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"
39 using namespace clang
;
41 using namespace ast_matchers
;
45 // ID of a node at which the diagnostic would be emitted.
46 const char *WarnAtNode
= "waitcall";
48 class GCDAntipatternChecker
: public Checker
<check::ASTCodeBody
> {
50 void checkASTCodeBody(const Decl
*D
,
52 BugReporter
&BR
) const;
55 decltype(auto) callsName(const char *FunctionName
) {
56 return callee(functionDecl(hasName(FunctionName
)));
59 decltype(auto) equalsBoundArgDecl(int ArgIdx
, const char *DeclName
) {
60 return hasArgument(ArgIdx
, ignoringParenCasts(declRefExpr(
61 to(varDecl(equalsBoundNode(DeclName
))))));
64 decltype(auto) bindAssignmentToDecl(const char *DeclName
) {
65 return hasLHS(ignoringParenImpCasts(
66 declRefExpr(to(varDecl().bind(DeclName
)))));
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
).startswith("test"))
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"))
90 static auto findGCDAntiPatternWithSemaphore() -> decltype(compoundStmt()) {
92 const char *SemaphoreBinding
= "semaphore_name";
93 auto SemaphoreCreateM
= callExpr(allOf(
94 callsName("dispatch_semaphore_create"),
95 hasArgument(0, ignoringParenCasts(integerLiteral(equals(0))))));
97 auto SemaphoreBindingM
= anyOf(
99 varDecl(hasDescendant(SemaphoreCreateM
)).bind(SemaphoreBinding
)),
100 forEachDescendant(binaryOperator(bindAssignmentToDecl(SemaphoreBinding
),
101 hasRHS(SemaphoreCreateM
))));
103 auto HasBlockArgumentM
= hasAnyArgument(hasType(
104 hasCanonicalType(blockPointerType())
107 auto ArgCallsSignalM
= hasAnyArgument(stmt(hasDescendant(callExpr(
109 callsName("dispatch_semaphore_signal"),
110 equalsBoundArgDecl(0, SemaphoreBinding
)
113 auto HasBlockAndCallsSignalM
= allOf(HasBlockArgumentM
, ArgCallsSignalM
);
115 auto HasBlockCallingSignalM
=
118 callExpr(HasBlockAndCallsSignalM
),
119 objcMessageExpr(HasBlockAndCallsSignalM
)
122 auto SemaphoreWaitM
= forEachDescendant(
125 callsName("dispatch_semaphore_wait"),
126 equalsBoundArgDecl(0, SemaphoreBinding
)
131 SemaphoreBindingM
, HasBlockCallingSignalM
, SemaphoreWaitM
);
134 static auto findGCDAntiPatternWithGroup() -> decltype(compoundStmt()) {
136 const char *GroupBinding
= "group_name";
137 auto DispatchGroupCreateM
= callExpr(callsName("dispatch_group_create"));
139 auto GroupBindingM
= anyOf(
141 varDecl(hasDescendant(DispatchGroupCreateM
)).bind(GroupBinding
)),
142 forEachDescendant(binaryOperator(bindAssignmentToDecl(GroupBinding
),
143 hasRHS(DispatchGroupCreateM
))));
145 auto GroupEnterM
= forEachDescendant(
146 stmt(callExpr(allOf(callsName("dispatch_group_enter"),
147 equalsBoundArgDecl(0, GroupBinding
)))));
149 auto HasBlockArgumentM
= hasAnyArgument(hasType(
150 hasCanonicalType(blockPointerType())
153 auto ArgCallsSignalM
= hasAnyArgument(stmt(hasDescendant(callExpr(
155 callsName("dispatch_group_leave"),
156 equalsBoundArgDecl(0, GroupBinding
)
159 auto HasBlockAndCallsLeaveM
= allOf(HasBlockArgumentM
, ArgCallsSignalM
);
164 callExpr(HasBlockAndCallsLeaveM
),
165 objcMessageExpr(HasBlockAndCallsLeaveM
)
168 auto GroupWaitM
= forEachDescendant(
171 callsName("dispatch_group_wait"),
172 equalsBoundArgDecl(0, GroupBinding
)
176 return compoundStmt(GroupBindingM
, GroupEnterM
, AcceptsBlockM
, GroupWaitM
);
179 static void emitDiagnostics(const BoundNodes
&Nodes
,
182 AnalysisDeclContext
*ADC
,
183 const GCDAntipatternChecker
*Checker
) {
184 const auto *SW
= Nodes
.getNodeAs
<CallExpr
>(WarnAtNode
);
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";
196 /*Name=*/"GCD performance anti-pattern",
197 /*BugCategory=*/"Performance",
199 PathDiagnosticLocation::createBegin(SW
, BR
.getSourceManager(), ADC
),
200 SW
->getSourceRange());
203 void GCDAntipatternChecker::checkASTCodeBody(const Decl
*D
,
205 BugReporter
&BR
) const {
209 AnalysisDeclContext
*ADC
= AM
.getAnalysisDeclContext(D
);
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);
216 auto GroupMatcherM
= findGCDAntiPatternWithGroup();
217 Matches
= match(GroupMatcherM
, *D
->getBody(), AM
.getASTContext());
218 for (BoundNodes Match
: Matches
)
219 emitDiagnostics(Match
, "group", BR
, ADC
, this);
222 } // end of anonymous namespace
224 void ento::registerGCDAntipattern(CheckerManager
&Mgr
) {
225 Mgr
.registerChecker
<GCDAntipatternChecker
>();
228 bool ento::shouldRegisterGCDAntipattern(const CheckerManager
&mgr
) {