clang  14.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 
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).startswith("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(
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 
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(
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 
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 }
clang::threadSafety::sx::equals
bool equals(const til::SExpr *E1, const til::SExpr *E2)
Definition: ThreadSafetyCommon.h:63
Nodes
BoundNodesTreeBuilder Nodes
Definition: ASTMatchFinder.cpp:82
string
string(SUBSTRING ${CMAKE_CURRENT_BINARY_DIR} 0 ${PATH_LIB_START} PATH_HEAD) string(SUBSTRING $
Definition: CMakeLists.txt:22
clang::ast_matchers::stmt
const internal::VariadicAllOfMatcher< Stmt > stmt
Matches statements.
Definition: ASTMatchersInternal.cpp:809
clang::ast_matchers::anyOf
const internal::VariadicOperatorMatcherFunc< 2, std::numeric_limits< unsigned >::max()> anyOf
Matches if any of the given matchers matches.
Definition: ASTMatchersInternal.cpp:988
clang::AnalysisDeclContext
AnalysisDeclContext contains the context data for the function, method or block under analysis.
Definition: AnalysisDeclContext.h:72
clang::ast_matchers::allOf
const internal::VariadicOperatorMatcherFunc< 2, std::numeric_limits< unsigned >::max()> allOf
Matches if all given matchers match.
Definition: ASTMatchersInternal.cpp:991
ASTMatchFinder.h
emitDiagnostics
static void emitDiagnostics(BoundNodes &Match, const Decl *D, BugReporter &BR, AnalysisManager &AM, const ObjCAutoreleaseWriteChecker *Checker)
Definition: ObjCAutoreleaseWriteChecker.cpp:109
clang::Type
The base class of the type hierarchy.
Definition: Type.h:1490
clang::ast_matchers::forEachDescendant
const internal::ArgumentAdaptingMatcherFunc< internal::ForEachDescendantMatcher > forEachDescendant
Matches AST nodes that have descendant AST nodes that match the provided matcher.
Definition: ASTMatchersInternal.cpp:1013
BuiltinCheckerRegistration.h
BugReporter.h
clang::ast_matchers::binaryOperator
const internal::VariadicDynCastAllOfMatcher< Stmt, BinaryOperator > binaryOperator
Matches binary operator expressions.
Definition: ASTMatchersInternal.cpp:946
clang::ast_matchers::compoundStmt
const internal::VariadicDynCastAllOfMatcher< Stmt, CompoundStmt > compoundStmt
Matches compound statements.
Definition: ASTMatchersInternal.cpp:910
BugType.h
clang::Decl
Decl - This represents one declaration (or definition), e.g.
Definition: DeclBase.h:89
clang::ast_matchers::objcMessageExpr
const internal::VariadicDynCastAllOfMatcher< Stmt, ObjCMessageExpr > objcMessageExpr
Matches ObjectiveC Message invocation expressions.
Definition: ASTMatchersInternal.cpp:821
clang::ast_matchers::blockPointerType
const AstTypeMatcher< BlockPointerType > blockPointerType
Matches block pointer types, i.e.
Definition: ASTMatchersInternal.cpp:1047
clang::ast_matchers::hasDescendant
const internal::ArgumentAdaptingMatcherFunc< internal::HasDescendantMatcher > hasDescendant
Matches AST nodes that have descendant AST nodes that match the provided matcher.
Definition: ASTMatchersInternal.cpp:1009
clang::ast_matchers::callExpr
const internal::VariadicDynCastAllOfMatcher< Stmt, CallExpr > callExpr
Matches call expressions.
Definition: ASTMatchersInternal.cpp:816
clang::ast_matchers::functionDecl
const internal::VariadicDynCastAllOfMatcher< Decl, FunctionDecl > functionDecl
Matches function declarations.
Definition: ASTMatchersInternal.cpp:805
clang::Decl::getBody
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:1010
clang::ast_matchers::declRefExpr
const internal::VariadicDynCastAllOfMatcher< Stmt, DeclRefExpr > declRefExpr
Matches expressions that refer to declarations.
Definition: ASTMatchersInternal.cpp:890
Checker.h
clang
Definition: CalledOnceCheck.h:17
clang::AnalysisDeclContext::getDecl
const Decl * getDecl() const
Definition: AnalysisDeclContext.h:106
clang::ast_matchers::match
SmallVector< BoundNodes, 1 > match(MatcherT Matcher, const NodeT &Node, ASTContext &Context)
Returns the results of matching Matcher on Node.
Definition: ASTMatchFinder.h:312
clang::ast_matchers::hasName
internal::Matcher< NamedDecl > hasName(StringRef Name)
Matches NamedDecl nodes that have the specified name.
Definition: ASTMatchers.h:2989
clang::ento::PathDiagnosticLocation::createBegin
static PathDiagnosticLocation createBegin(const Decl *D, const SourceManager &SM)
Create a location for the beginning of the declaration.
Definition: PathDiagnostic.cpp:580
clang::ast_matchers::BoundNodes
Maps string IDs to AST nodes matched by parts of a matcher.
Definition: ASTMatchers.h:107
clang::ast_matchers::integerLiteral
const internal::VariadicDynCastAllOfMatcher< Stmt, IntegerLiteral > integerLiteral
Matches integer literals of all sizes / encodings, e.g.
Definition: ASTMatchersInternal.cpp:922
clang::ast_matchers::varDecl
const internal::VariadicDynCastAllOfMatcher< Decl, VarDecl > varDecl
Matches variable declarations.
Definition: ASTMatchersInternal.cpp:801
AnalysisManager.h
clang::CallExpr
CallExpr - Represents a function call (C99 6.5.2.2, C++ [expr.call]).
Definition: Expr.h:2795
clang::ento::ObjKind::OS
@ OS
Indicates that the tracking object is a descendant of a referenced-counted OSObject,...