clang  9.0.0
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 
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 auto callsName(const char *FunctionName)
56  -> decltype(callee(functionDecl())) {
57  return callee(functionDecl(hasName(FunctionName)));
58 }
59 
60 auto equalsBoundArgDecl(int ArgIdx, const char *DeclName)
61  -> decltype(hasArgument(0, expr())) {
62  return hasArgument(ArgIdx, ignoringParenCasts(declRefExpr(
63  to(varDecl(equalsBoundNode(DeclName))))));
64 }
65 
66 auto bindAssignmentToDecl(const char *DeclName) -> decltype(hasLHS(expr())) {
67  return hasLHS(ignoringParenImpCasts(
68  declRefExpr(to(varDecl().bind(DeclName)))));
69 }
70 
71 /// The pattern is very common in tests, and it is OK to use it there.
72 /// We have to heuristics for detecting tests: method name starts with "test"
73 /// (used in XCTest), and a class name contains "mock" or "test" (used in
74 /// helpers which are not tests themselves, but used exclusively in tests).
75 static bool isTest(const Decl *D) {
76  if (const auto* ND = dyn_cast<NamedDecl>(D)) {
77  std::string DeclName = ND->getNameAsString();
78  if (StringRef(DeclName).startswith("test"))
79  return true;
80  }
81  if (const auto *OD = dyn_cast<ObjCMethodDecl>(D)) {
82  if (const auto *CD = dyn_cast<ObjCContainerDecl>(OD->getParent())) {
83  std::string ContainerName = CD->getNameAsString();
84  StringRef CN(ContainerName);
85  if (CN.contains_lower("test") || CN.contains_lower("mock"))
86  return true;
87  }
88  }
89  return false;
90 }
91 
92 static auto findGCDAntiPatternWithSemaphore() -> decltype(compoundStmt()) {
93 
94  const char *SemaphoreBinding = "semaphore_name";
95  auto SemaphoreCreateM = callExpr(allOf(
96  callsName("dispatch_semaphore_create"),
97  hasArgument(0, ignoringParenCasts(integerLiteral(equals(0))))));
98 
99  auto SemaphoreBindingM = anyOf(
101  varDecl(hasDescendant(SemaphoreCreateM)).bind(SemaphoreBinding)),
102  forEachDescendant(binaryOperator(bindAssignmentToDecl(SemaphoreBinding),
103  hasRHS(SemaphoreCreateM))));
104 
105  auto HasBlockArgumentM = hasAnyArgument(hasType(
106  hasCanonicalType(blockPointerType())
107  ));
108 
109  auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr(
110  allOf(
111  callsName("dispatch_semaphore_signal"),
112  equalsBoundArgDecl(0, SemaphoreBinding)
113  )))));
114 
115  auto HasBlockAndCallsSignalM = allOf(HasBlockArgumentM, ArgCallsSignalM);
116 
117  auto HasBlockCallingSignalM =
119  stmt(anyOf(
120  callExpr(HasBlockAndCallsSignalM),
121  objcMessageExpr(HasBlockAndCallsSignalM)
122  )));
123 
124  auto SemaphoreWaitM = forEachDescendant(
125  callExpr(
126  allOf(
127  callsName("dispatch_semaphore_wait"),
128  equalsBoundArgDecl(0, SemaphoreBinding)
129  )
130  ).bind(WarnAtNode));
131 
132  return compoundStmt(
133  SemaphoreBindingM, HasBlockCallingSignalM, SemaphoreWaitM);
134 }
135 
136 static auto findGCDAntiPatternWithGroup() -> decltype(compoundStmt()) {
137 
138  const char *GroupBinding = "group_name";
139  auto DispatchGroupCreateM = callExpr(callsName("dispatch_group_create"));
140 
141  auto GroupBindingM = anyOf(
143  varDecl(hasDescendant(DispatchGroupCreateM)).bind(GroupBinding)),
144  forEachDescendant(binaryOperator(bindAssignmentToDecl(GroupBinding),
145  hasRHS(DispatchGroupCreateM))));
146 
147  auto GroupEnterM = forEachDescendant(
148  stmt(callExpr(allOf(callsName("dispatch_group_enter"),
149  equalsBoundArgDecl(0, GroupBinding)))));
150 
151  auto HasBlockArgumentM = hasAnyArgument(hasType(
152  hasCanonicalType(blockPointerType())
153  ));
154 
155  auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr(
156  allOf(
157  callsName("dispatch_group_leave"),
158  equalsBoundArgDecl(0, GroupBinding)
159  )))));
160 
161  auto HasBlockAndCallsLeaveM = allOf(HasBlockArgumentM, ArgCallsSignalM);
162 
163  auto AcceptsBlockM =
165  stmt(anyOf(
166  callExpr(HasBlockAndCallsLeaveM),
167  objcMessageExpr(HasBlockAndCallsLeaveM)
168  )));
169 
170  auto GroupWaitM = forEachDescendant(
171  callExpr(
172  allOf(
173  callsName("dispatch_group_wait"),
174  equalsBoundArgDecl(0, GroupBinding)
175  )
176  ).bind(WarnAtNode));
177 
178  return compoundStmt(GroupBindingM, GroupEnterM, AcceptsBlockM, GroupWaitM);
179 }
180 
181 static void emitDiagnostics(const BoundNodes &Nodes,
182  const char* Type,
183  BugReporter &BR,
184  AnalysisDeclContext *ADC,
185  const GCDAntipatternChecker *Checker) {
186  const auto *SW = Nodes.getNodeAs<CallExpr>(WarnAtNode);
187  assert(SW);
188 
189  std::string Diagnostics;
190  llvm::raw_string_ostream OS(Diagnostics);
191  OS << "Waiting on a callback using a " << Type << " creates useless threads "
192  << "and is subject to priority inversion; consider "
193  << "using a synchronous API or changing the caller to be asynchronous";
194 
195  BR.EmitBasicReport(
196  ADC->getDecl(),
197  Checker,
198  /*Name=*/"GCD performance anti-pattern",
199  /*BugCategory=*/"Performance",
200  OS.str(),
201  PathDiagnosticLocation::createBegin(SW, BR.getSourceManager(), ADC),
202  SW->getSourceRange());
203 }
204 
205 void GCDAntipatternChecker::checkASTCodeBody(const Decl *D,
206  AnalysisManager &AM,
207  BugReporter &BR) const {
208  if (isTest(D))
209  return;
210 
211  AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);
212 
213  auto SemaphoreMatcherM = findGCDAntiPatternWithSemaphore();
214  auto Matches = match(SemaphoreMatcherM, *D->getBody(), AM.getASTContext());
215  for (BoundNodes Match : Matches)
216  emitDiagnostics(Match, "semaphore", BR, ADC, this);
217 
218  auto GroupMatcherM = findGCDAntiPatternWithGroup();
219  Matches = match(GroupMatcherM, *D->getBody(), AM.getASTContext());
220  for (BoundNodes Match : Matches)
221  emitDiagnostics(Match, "group", BR, ADC, this);
222 }
223 
224 } // end of anonymous namespace
225 
226 void ento::registerGCDAntipattern(CheckerManager &Mgr) {
227  Mgr.registerChecker<GCDAntipatternChecker>();
228 }
229 
230 bool ento::shouldRegisterGCDAntipattern(const LangOptions &LO) {
231  return true;
232 }
const internal::VariadicDynCastAllOfMatcher< Stmt, CallExpr > callExpr
Matches call expressions.
const internal::VariadicAllOfMatcher< Stmt > stmt
Matches statements.
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:986
const internal::VariadicOperatorMatcherFunc< 2, std::numeric_limits< unsigned >::max()> anyOf
Matches if any of the given matchers matches.
Decl - This represents one declaration (or definition), e.g.
Definition: DeclBase.h:88
const internal::ArgumentAdaptingMatcherFunc< internal::HasDescendantMatcher > hasDescendant
Matches AST nodes that have descendant AST nodes that match the provided matcher. ...
The base class of the type hierarchy.
Definition: Type.h:1433
const internal::VariadicDynCastAllOfMatcher< Stmt, Expr > expr
Matches expressions.
const internal::VariadicOperatorMatcherFunc< 2, std::numeric_limits< unsigned >::max()> allOf
Matches if all given matchers match.
const internal::VariadicDynCastAllOfMatcher< Stmt, BinaryOperator > binaryOperator
Matches binary operator expressions.
const internal::VariadicDynCastAllOfMatcher< Decl, VarDecl > varDecl
Matches variable declarations.
const internal::VariadicDynCastAllOfMatcher< Decl, FunctionDecl > functionDecl
Matches function declarations.
BoundNodesTreeBuilder Nodes
AnalysisDeclContext contains the context data for the function or method under analysis.
Keeps track of the various options that can be enabled, which controls the dialect of C or C++ that i...
Definition: LangOptions.h:49
const internal::VariadicDynCastAllOfMatcher< Stmt, DeclRefExpr > declRefExpr
Matches expressions that refer to declarations.
const T * getNodeAs(StringRef ID) const
Returns the AST node bound to ID.
Definition: ASTMatchers.h:110
SmallVector< BoundNodes, 1 > match(MatcherT Matcher, const NodeT &Node, ASTContext &Context)
Returns the results of matching Matcher on Node.
const internal::ArgumentAdaptingMatcherFunc< internal::ForEachDescendantMatcher > forEachDescendant
Matches AST nodes that have descendant AST nodes that match the provided matcher. ...
bool equals(const til::SExpr *E1, const til::SExpr *E2)
Maps string IDs to AST nodes matched by parts of a matcher.
Definition: ASTMatchers.h:103
static PathDiagnosticLocation createBegin(const Decl *D, const SourceManager &SM)
Create a location for the beginning of the declaration.
const internal::VariadicDynCastAllOfMatcher< Stmt, IntegerLiteral > integerLiteral
Matches integer literals of all sizes / encodings, e.g.
const Decl * getDecl() const
static void emitDiagnostics(BoundNodes &Match, const Decl *D, BugReporter &BR, AnalysisManager &AM, const ObjCAutoreleaseWriteChecker *Checker)
Dataflow Directional Tag Classes.
Indicates that the tracking object is a descendant of a referenced-counted OSObject, used in the Darwin kernel.
internal::Matcher< NamedDecl > hasName(const std::string &Name)
Matches NamedDecl nodes that have the specified name.
Definition: ASTMatchers.h:2545
CallExpr - Represents a function call (C99 6.5.2.2, C++ [expr.call]).
Definition: Expr.h:2516
const internal::VariadicDynCastAllOfMatcher< Stmt, CompoundStmt > compoundStmt
Matches compound statements.
const internal::VariadicDynCastAllOfMatcher< Stmt, ObjCMessageExpr > objcMessageExpr
Matches ObjectiveC Message invocation expressions.
const AstTypeMatcher< BlockPointerType > blockPointerType
Matches block pointer types, i.e.