clang 20.0.0git
GCDAntipatternChecker.cpp
Go to the documentation of this file.
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
37#include "llvm/Support/Debug.h"
38
39using namespace clang;
40using namespace ento;
41using namespace ast_matchers;
42
43namespace {
44
45// ID of a node at which the diagnostic would be emitted.
46const char *WarnAtNode = "waitcall";
47
48class GCDAntipatternChecker : public Checker<check::ASTCodeBody> {
49public:
50 void checkASTCodeBody(const Decl *D,
52 BugReporter &BR) const;
53};
54
55decltype(auto) callsName(const char *FunctionName) {
56 return callee(functionDecl(hasName(FunctionName)));
57}
58
59decltype(auto) equalsBoundArgDecl(int ArgIdx, const char *DeclName) {
60 return hasArgument(ArgIdx, ignoringParenCasts(declRefExpr(
61 to(varDecl(equalsBoundNode(DeclName))))));
62}
63
64decltype(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).
73static 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
90static 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(
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 =
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
134static auto findGCDAntiPatternWithGroup() -> decltype(compoundStmt()) {
135
136 const char *GroupBinding = "group_name";
137 auto DispatchGroupCreateM = callExpr(callsName("dispatch_group_create"));
138
139 auto GroupBindingM = anyOf(
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 =
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
179static void emitDiagnostics(const BoundNodes &Nodes,
180 const char* Type,
181 BugReporter &BR,
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
194 ADC->getDecl(),
195 Checker,
196 /*Name=*/"GCD performance anti-pattern",
197 /*BugCategory=*/"Performance",
198 OS.str(),
200 SW->getSourceRange());
201}
202
203void GCDAntipatternChecker::checkASTCodeBody(const Decl *D,
204 AnalysisManager &AM,
205 BugReporter &BR) const {
206 if (isTest(D))
207 return;
208
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
224void ento::registerGCDAntipattern(CheckerManager &Mgr) {
225 Mgr.registerChecker<GCDAntipatternChecker>();
226}
227
228bool ento::shouldRegisterGCDAntipattern(const CheckerManager &mgr) {
229 return true;
230}
BoundNodesTreeBuilder Nodes
const Decl * D
static void emitDiagnostics(BoundNodes &Match, const Decl *D, BugReporter &BR, AnalysisManager &AM, const ObjCAutoreleaseWriteChecker *Checker)
AnalysisDeclContext contains the context data for the function, method or block under analysis.
const Decl * getDecl() const
CallExpr - Represents a function call (C99 6.5.2.2, C++ [expr.call]).
Definition: Expr.h:2830
Decl - This represents one declaration (or definition), e.g.
Definition: DeclBase.h:86
virtual Stmt * getBody() const
getBody - If this Decl represents a declaration for a body of code, such as a function or method defi...
Definition: DeclBase.h:1077
The base class of the type hierarchy.
Definition: Type.h:1829
Maps string IDs to AST nodes matched by parts of a matcher.
Definition: ASTMatchers.h:109
ASTContext & getASTContext() override
AnalysisDeclContext * getAnalysisDeclContext(const Decl *D)
BugReporter is a utility class for generating PathDiagnostics for analysis.
Definition: BugReporter.h:585
const SourceManager & getSourceManager()
Definition: BugReporter.h:623
void EmitBasicReport(const Decl *DeclWithIssue, const CheckerBase *Checker, StringRef BugName, StringRef BugCategory, StringRef BugStr, PathDiagnosticLocation Loc, ArrayRef< SourceRange > Ranges=std::nullopt, ArrayRef< FixItHint > Fixits=std::nullopt)
CHECKER * registerChecker(AT &&... Args)
Used to register checkers.
static PathDiagnosticLocation createBegin(const Decl *D, const SourceManager &SM)
Create a location for the beginning of the declaration.
const internal::VariadicDynCastAllOfMatcher< Decl, VarDecl > varDecl
Matches variable declarations.
const internal::VariadicDynCastAllOfMatcher< Stmt, DeclRefExpr > declRefExpr
Matches expressions that refer to declarations.
const internal::ArgumentAdaptingMatcherFunc< internal::HasDescendantMatcher > hasDescendant
Matches AST nodes that have descendant AST nodes that match the provided matcher.
internal::Matcher< NamedDecl > hasName(StringRef Name)
Matches NamedDecl nodes that have the specified name.
Definition: ASTMatchers.h:3079
const internal::VariadicDynCastAllOfMatcher< Stmt, CallExpr > callExpr
Matches call expressions.
const internal::VariadicDynCastAllOfMatcher< Stmt, CompoundStmt > compoundStmt
Matches compound statements.
const internal::ArgumentAdaptingMatcherFunc< internal::ForEachDescendantMatcher > forEachDescendant
Matches AST nodes that have descendant AST nodes that match the provided matcher.
SmallVector< BoundNodes, 1 > match(MatcherT Matcher, const NodeT &Node, ASTContext &Context)
Returns the results of matching Matcher on Node.
const internal::VariadicDynCastAllOfMatcher< Stmt, ObjCMessageExpr > objcMessageExpr
Matches ObjectiveC Message invocation expressions.
const AstTypeMatcher< BlockPointerType > blockPointerType
Matches block pointer types, i.e.
const internal::VariadicDynCastAllOfMatcher< Stmt, BinaryOperator > binaryOperator
Matches binary operator expressions.
internal::PolymorphicMatcher< internal::ValueEqualsMatcher, void(internal::AllNodeBaseTypes), ValueT > equals(const ValueT &Value)
Matches literals that are equal to the given value of type ValueT.
Definition: ASTMatchers.h:5848
const internal::VariadicOperatorMatcherFunc< 2, std::numeric_limits< unsigned >::max()> allOf
Matches if all given matchers match.
const internal::VariadicDynCastAllOfMatcher< Decl, FunctionDecl > functionDecl
Matches function declarations.
const internal::VariadicDynCastAllOfMatcher< Stmt, IntegerLiteral > integerLiteral
Matches integer literals of all sizes / encodings, e.g.
const internal::VariadicAllOfMatcher< Stmt > stmt
Matches statements.
const internal::VariadicOperatorMatcherFunc< 2, std::numeric_limits< unsigned >::max()> anyOf
Matches if any of the given matchers matches.
The JSON file list parser is used to communicate input to InstallAPI.