Clang Project

clang_source_code/lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp
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
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"
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,
51                        AnalysisManager &AM,
52                        BugReporter &BRconst;
53};
54
55auto callsName(const char *FunctionName)
56    -> decltype(callee(functionDecl())) {
57  return callee(functionDecl(hasName(FunctionName)));
58}
59
60auto equalsBoundArgDecl(int ArgIdx, const char *DeclName)
61    -> decltype(hasArgument(0, expr())) {
62  return hasArgument(ArgIdx, ignoringParenCasts(declRefExpr(
63                                 to(varDecl(equalsBoundNode(DeclName))))));
64}
65
66auto 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).
75static 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
92static 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(
100      forEachDescendant(
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 =
118    forEachDescendant(
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
136static auto findGCDAntiPatternWithGroup() -> decltype(compoundStmt()) {
137
138  const char *GroupBinding = "group_name";
139  auto DispatchGroupCreateM = callExpr(callsName("dispatch_group_create"));
140
141  auto GroupBindingM = anyOf(
142      forEachDescendant(
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 =
164    forEachDescendant(
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
181static void emitDiagnostics(const BoundNodes &Nodes,
182                            const charType,
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    /*Category=*/"Performance",
200    OS.str(),
201    PathDiagnosticLocation::createBegin(SW, BR.getSourceManager(), ADC),
202    SW->getSourceRange());
203}
204
205void GCDAntipatternChecker::checkASTCodeBody(const Decl *D,
206                                             AnalysisManager &AM,
207                                             BugReporter &BRconst {
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
226void ento::registerGCDAntipattern(CheckerManager &Mgr) {
227  Mgr.registerChecker<GCDAntipatternChecker>();
228}
229
230bool ento::shouldRegisterGCDAntipattern(const LangOptions &LO) {
231  return true;
232}
233