clang  7.0.0
GCDAntipatternChecker.cpp
Go to the documentation of this file.
1 //===- GCDAntipatternChecker.cpp ---------------------------------*- C++ -*-==//
2 //
3 // The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 //
10 // This file defines GCDAntipatternChecker which checks against a common
11 // antipattern when synchronous API is emulated from asynchronous callbacks
12 // using a semaphore:
13 //
14 // dispatch_semaphore_t sema = dispatch_semaphore_create(0);
15 //
16 // AnyCFunctionCall(^{
17 // // code…
18 // dispatch_semaphore_signal(sema);
19 // })
20 // dispatch_semaphore_wait(sema, *)
21 //
22 // Such code is a common performance problem, due to inability of GCD to
23 // properly handle QoS when a combination of queues and semaphores is used.
24 // Good code would either use asynchronous API (when available), or perform
25 // the necessary action in asynchronous callback.
26 //
27 // Currently, the check is performed using a simple heuristical AST pattern
28 // matching.
29 //
30 //===----------------------------------------------------------------------===//
31 
32 #include "ClangSACheckers.h"
38 #include "llvm/Support/Debug.h"
39 
40 using namespace clang;
41 using namespace ento;
42 using namespace ast_matchers;
43 
44 namespace {
45 
46 // ID of a node at which the diagnostic would be emitted.
47 const char *WarnAtNode = "waitcall";
48 
49 class GCDAntipatternChecker : public Checker<check::ASTCodeBody> {
50 public:
51  void checkASTCodeBody(const Decl *D,
52  AnalysisManager &AM,
53  BugReporter &BR) const;
54 };
55 
56 auto callsName(const char *FunctionName)
57  -> decltype(callee(functionDecl())) {
58  return callee(functionDecl(hasName(FunctionName)));
59 }
60 
61 auto equalsBoundArgDecl(int ArgIdx, const char *DeclName)
62  -> decltype(hasArgument(0, expr())) {
63  return hasArgument(ArgIdx, ignoringParenCasts(declRefExpr(
64  to(varDecl(equalsBoundNode(DeclName))))));
65 }
66 
67 auto bindAssignmentToDecl(const char *DeclName) -> decltype(hasLHS(expr())) {
68  return hasLHS(ignoringParenImpCasts(
69  declRefExpr(to(varDecl().bind(DeclName)))));
70 }
71 
72 /// The pattern is very common in tests, and it is OK to use it there.
73 /// We have to heuristics for detecting tests: method name starts with "test"
74 /// (used in XCTest), and a class name contains "mock" or "test" (used in
75 /// helpers which are not tests themselves, but used exclusively in tests).
76 static bool isTest(const Decl *D) {
77  if (const auto* ND = dyn_cast<NamedDecl>(D)) {
78  std::string DeclName = ND->getNameAsString();
79  if (StringRef(DeclName).startswith("test"))
80  return true;
81  }
82  if (const auto *OD = dyn_cast<ObjCMethodDecl>(D)) {
83  if (const auto *CD = dyn_cast<ObjCContainerDecl>(OD->getParent())) {
84  std::string ContainerName = CD->getNameAsString();
85  StringRef CN(ContainerName);
86  if (CN.contains_lower("test") || CN.contains_lower("mock"))
87  return true;
88  }
89  }
90  return false;
91 }
92 
93 static auto findGCDAntiPatternWithSemaphore() -> decltype(compoundStmt()) {
94 
95  const char *SemaphoreBinding = "semaphore_name";
96  auto SemaphoreCreateM = callExpr(allOf(
97  callsName("dispatch_semaphore_create"),
98  hasArgument(0, ignoringParenCasts(integerLiteral(equals(0))))));
99 
100  auto SemaphoreBindingM = anyOf(
102  varDecl(hasDescendant(SemaphoreCreateM)).bind(SemaphoreBinding)),
103  forEachDescendant(binaryOperator(bindAssignmentToDecl(SemaphoreBinding),
104  hasRHS(SemaphoreCreateM))));
105 
106  auto HasBlockArgumentM = hasAnyArgument(hasType(
107  hasCanonicalType(blockPointerType())
108  ));
109 
110  auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr(
111  allOf(
112  callsName("dispatch_semaphore_signal"),
113  equalsBoundArgDecl(0, SemaphoreBinding)
114  )))));
115 
116  auto HasBlockAndCallsSignalM = allOf(HasBlockArgumentM, ArgCallsSignalM);
117 
118  auto HasBlockCallingSignalM =
120  stmt(anyOf(
121  callExpr(HasBlockAndCallsSignalM),
122  objcMessageExpr(HasBlockAndCallsSignalM)
123  )));
124 
125  auto SemaphoreWaitM = forEachDescendant(
126  callExpr(
127  allOf(
128  callsName("dispatch_semaphore_wait"),
129  equalsBoundArgDecl(0, SemaphoreBinding)
130  )
131  ).bind(WarnAtNode));
132 
133  return compoundStmt(
134  SemaphoreBindingM, HasBlockCallingSignalM, SemaphoreWaitM);
135 }
136 
137 static auto findGCDAntiPatternWithGroup() -> decltype(compoundStmt()) {
138 
139  const char *GroupBinding = "group_name";
140  auto DispatchGroupCreateM = callExpr(callsName("dispatch_group_create"));
141 
142  auto GroupBindingM = anyOf(
144  varDecl(hasDescendant(DispatchGroupCreateM)).bind(GroupBinding)),
145  forEachDescendant(binaryOperator(bindAssignmentToDecl(GroupBinding),
146  hasRHS(DispatchGroupCreateM))));
147 
148  auto GroupEnterM = forEachDescendant(
149  stmt(callExpr(allOf(callsName("dispatch_group_enter"),
150  equalsBoundArgDecl(0, GroupBinding)))));
151 
152  auto HasBlockArgumentM = hasAnyArgument(hasType(
153  hasCanonicalType(blockPointerType())
154  ));
155 
156  auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr(
157  allOf(
158  callsName("dispatch_group_leave"),
159  equalsBoundArgDecl(0, GroupBinding)
160  )))));
161 
162  auto HasBlockAndCallsLeaveM = allOf(HasBlockArgumentM, ArgCallsSignalM);
163 
164  auto AcceptsBlockM =
166  stmt(anyOf(
167  callExpr(HasBlockAndCallsLeaveM),
168  objcMessageExpr(HasBlockAndCallsLeaveM)
169  )));
170 
171  auto GroupWaitM = forEachDescendant(
172  callExpr(
173  allOf(
174  callsName("dispatch_group_wait"),
175  equalsBoundArgDecl(0, GroupBinding)
176  )
177  ).bind(WarnAtNode));
178 
179  return compoundStmt(GroupBindingM, GroupEnterM, AcceptsBlockM, GroupWaitM);
180 }
181 
182 static void emitDiagnostics(const BoundNodes &Nodes,
183  const char* Type,
184  BugReporter &BR,
185  AnalysisDeclContext *ADC,
186  const GCDAntipatternChecker *Checker) {
187  const auto *SW = Nodes.getNodeAs<CallExpr>(WarnAtNode);
188  assert(SW);
189 
190  std::string Diagnostics;
191  llvm::raw_string_ostream OS(Diagnostics);
192  OS << "Waiting on a callback using a " << Type << " creates useless threads "
193  << "and is subject to priority inversion; consider "
194  << "using a synchronous API or changing the caller to be asynchronous";
195 
196  BR.EmitBasicReport(
197  ADC->getDecl(),
198  Checker,
199  /*Name=*/"GCD performance anti-pattern",
200  /*Category=*/"Performance",
201  OS.str(),
203  SW->getSourceRange());
204 }
205 
206 void GCDAntipatternChecker::checkASTCodeBody(const Decl *D,
207  AnalysisManager &AM,
208  BugReporter &BR) const {
209  if (isTest(D))
210  return;
211 
213 
214  auto SemaphoreMatcherM = findGCDAntiPatternWithSemaphore();
215  auto Matches = match(SemaphoreMatcherM, *D->getBody(), AM.getASTContext());
216  for (BoundNodes Match : Matches)
217  emitDiagnostics(Match, "semaphore", BR, ADC, this);
218 
219  auto GroupMatcherM = findGCDAntiPatternWithGroup();
220  Matches = match(GroupMatcherM, *D->getBody(), AM.getASTContext());
221  for (BoundNodes Match : Matches)
222  emitDiagnostics(Match, "group", BR, ADC, this);
223 }
224 
225 }
226 
227 void ento::registerGCDAntipattern(CheckerManager &Mgr) {
228  Mgr.registerChecker<GCDAntipatternChecker>();
229 }
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:86
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:1428
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.
const internal::VariadicDynCastAllOfMatcher< Stmt, DeclRefExpr > declRefExpr
Matches expressions that refer to declarations.
SmallVector< BoundNodes, 1 > match(MatcherT Matcher, const NodeT &Node, ASTContext &Context)
Returns the results of matching Matcher on Node.
ASTContext & getASTContext() override
AnalysisDeclContext * getAnalysisDeclContext(const Decl *D)
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)
CHECKER * registerChecker(AT... Args)
Used to register checkers.
BugReporter is a utility class for generating PathDiagnostics for analysis.
Definition: BugReporter.h:412
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)
BoundNodesTreeBuilder BoundNodes
Dataflow Directional Tag Classes.
SourceManager & getSourceManager()
Definition: BugReporter.h:474
internal::Matcher< NamedDecl > hasName(const std::string &Name)
Matches NamedDecl nodes that have the specified name.
Definition: ASTMatchers.h:2361
CallExpr - Represents a function call (C99 6.5.2.2, C++ [expr.call]).
Definition: Expr.h:2316
const internal::VariadicDynCastAllOfMatcher< Stmt, CompoundStmt > compoundStmt
Matches compound statements.
void EmitBasicReport(const Decl *DeclWithIssue, const CheckerBase *Checker, StringRef BugName, StringRef BugCategory, StringRef BugStr, PathDiagnosticLocation Loc, ArrayRef< SourceRange > Ranges=None)
const internal::VariadicDynCastAllOfMatcher< Stmt, ObjCMessageExpr > objcMessageExpr
Matches ObjectiveC Message invocation expressions.
const AstTypeMatcher< BlockPointerType > blockPointerType
Matches block pointer types, i.e.