Coverage Report

Created: 2019-07-24 05:18

/Users/buildslave/jenkins/workspace/clang-stage2-coverage-R/llvm/tools/clang/lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp
Line
Count
Source
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
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
525
    -> decltype(callee(functionDecl())) {
57
525
  return callee(functionDecl(hasName(FunctionName)));
58
525
}
59
60
auto equalsBoundArgDecl(int ArgIdx, const char *DeclName)
61
375
    -> decltype(hasArgument(0, expr())) {
62
375
  return hasArgument(ArgIdx, ignoringParenCasts(declRefExpr(
63
375
                                 to(varDecl(equalsBoundNode(DeclName))))));
64
375
}
65
66
150
auto bindAssignmentToDecl(const char *DeclName) -> decltype(hasLHS(expr())) {
67
150
  return hasLHS(ignoringParenImpCasts(
68
150
                         declRefExpr(to(varDecl().bind(DeclName)))));
69
150
}
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
79
static bool isTest(const Decl *D) {
76
79
  if (const auto* ND = dyn_cast<NamedDecl>(D)) {
77
49
    std::string DeclName = ND->getNameAsString();
78
49
    if (StringRef(DeclName).startswith("test"))
79
2
      return true;
80
77
  }
81
77
  if (const auto *OD = dyn_cast<ObjCMethodDecl>(D)) {
82
8
    if (const auto *CD = dyn_cast<ObjCContainerDecl>(OD->getParent())) {
83
8
      std::string ContainerName = CD->getNameAsString();
84
8
      StringRef CN(ContainerName);
85
8
      if (CN.contains_lower("test") || 
CN.contains_lower("mock")7
)
86
2
        return true;
87
75
    }
88
8
  }
89
75
  return false;
90
75
}
91
92
75
static auto findGCDAntiPatternWithSemaphore() -> decltype(compoundStmt()) {
93
75
94
75
  const char *SemaphoreBinding = "semaphore_name";
95
75
  auto SemaphoreCreateM = callExpr(allOf(
96
75
      callsName("dispatch_semaphore_create"),
97
75
      hasArgument(0, ignoringParenCasts(integerLiteral(equals(0))))));
98
75
99
75
  auto SemaphoreBindingM = anyOf(
100
75
      forEachDescendant(
101
75
          varDecl(hasDescendant(SemaphoreCreateM)).bind(SemaphoreBinding)),
102
75
      forEachDescendant(binaryOperator(bindAssignmentToDecl(SemaphoreBinding),
103
75
                     hasRHS(SemaphoreCreateM))));
104
75
105
75
  auto HasBlockArgumentM = hasAnyArgument(hasType(
106
75
            hasCanonicalType(blockPointerType())
107
75
            ));
108
75
109
75
  auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr(
110
75
          allOf(
111
75
              callsName("dispatch_semaphore_signal"),
112
75
              equalsBoundArgDecl(0, SemaphoreBinding)
113
75
              )))));
114
75
115
75
  auto HasBlockAndCallsSignalM = allOf(HasBlockArgumentM, ArgCallsSignalM);
116
75
117
75
  auto HasBlockCallingSignalM =
118
75
    forEachDescendant(
119
75
      stmt(anyOf(
120
75
        callExpr(HasBlockAndCallsSignalM),
121
75
        objcMessageExpr(HasBlockAndCallsSignalM)
122
75
           )));
123
75
124
75
  auto SemaphoreWaitM = forEachDescendant(
125
75
    callExpr(
126
75
      allOf(
127
75
        callsName("dispatch_semaphore_wait"),
128
75
        equalsBoundArgDecl(0, SemaphoreBinding)
129
75
      )
130
75
    ).bind(WarnAtNode));
131
75
132
75
  return compoundStmt(
133
75
      SemaphoreBindingM, HasBlockCallingSignalM, SemaphoreWaitM);
134
75
}
135
136
75
static auto findGCDAntiPatternWithGroup() -> decltype(compoundStmt()) {
137
75
138
75
  const char *GroupBinding = "group_name";
139
75
  auto DispatchGroupCreateM = callExpr(callsName("dispatch_group_create"));
140
75
141
75
  auto GroupBindingM = anyOf(
142
75
      forEachDescendant(
143
75
          varDecl(hasDescendant(DispatchGroupCreateM)).bind(GroupBinding)),
144
75
      forEachDescendant(binaryOperator(bindAssignmentToDecl(GroupBinding),
145
75
                     hasRHS(DispatchGroupCreateM))));
146
75
147
75
  auto GroupEnterM = forEachDescendant(
148
75
      stmt(callExpr(allOf(callsName("dispatch_group_enter"),
149
75
                          equalsBoundArgDecl(0, GroupBinding)))));
150
75
151
75
  auto HasBlockArgumentM = hasAnyArgument(hasType(
152
75
            hasCanonicalType(blockPointerType())
153
75
            ));
154
75
155
75
  auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr(
156
75
          allOf(
157
75
              callsName("dispatch_group_leave"),
158
75
              equalsBoundArgDecl(0, GroupBinding)
159
75
              )))));
160
75
161
75
  auto HasBlockAndCallsLeaveM = allOf(HasBlockArgumentM, ArgCallsSignalM);
162
75
163
75
  auto AcceptsBlockM =
164
75
    forEachDescendant(
165
75
      stmt(anyOf(
166
75
        callExpr(HasBlockAndCallsLeaveM),
167
75
        objcMessageExpr(HasBlockAndCallsLeaveM)
168
75
           )));
169
75
170
75
  auto GroupWaitM = forEachDescendant(
171
75
    callExpr(
172
75
      allOf(
173
75
        callsName("dispatch_group_wait"),
174
75
        equalsBoundArgDecl(0, GroupBinding)
175
75
      )
176
75
    ).bind(WarnAtNode));
177
75
178
75
  return compoundStmt(GroupBindingM, GroupEnterM, AcceptsBlockM, GroupWaitM);
179
75
}
180
181
static void emitDiagnostics(const BoundNodes &Nodes,
182
                            const char* Type,
183
                            BugReporter &BR,
184
                            AnalysisDeclContext *ADC,
185
22
                            const GCDAntipatternChecker *Checker) {
186
22
  const auto *SW = Nodes.getNodeAs<CallExpr>(WarnAtNode);
187
22
  assert(SW);
188
22
189
22
  std::string Diagnostics;
190
22
  llvm::raw_string_ostream OS(Diagnostics);
191
22
  OS << "Waiting on a callback using a " << Type << " creates useless threads "
192
22
     << "and is subject to priority inversion; consider "
193
22
     << "using a synchronous API or changing the caller to be asynchronous";
194
22
195
22
  BR.EmitBasicReport(
196
22
    ADC->getDecl(),
197
22
    Checker,
198
22
    /*Name=*/"GCD performance anti-pattern",
199
22
    /*BugCategory=*/"Performance",
200
22
    OS.str(),
201
22
    PathDiagnosticLocation::createBegin(SW, BR.getSourceManager(), ADC),
202
22
    SW->getSourceRange());
203
22
}
204
205
void GCDAntipatternChecker::checkASTCodeBody(const Decl *D,
206
                                             AnalysisManager &AM,
207
79
                                             BugReporter &BR) const {
208
79
  if (isTest(D))
209
4
    return;
210
75
211
75
  AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);
212
75
213
75
  auto SemaphoreMatcherM = findGCDAntiPatternWithSemaphore();
214
75
  auto Matches = match(SemaphoreMatcherM, *D->getBody(), AM.getASTContext());
215
75
  for (BoundNodes Match : Matches)
216
18
    emitDiagnostics(Match, "semaphore", BR, ADC, this);
217
75
218
75
  auto GroupMatcherM = findGCDAntiPatternWithGroup();
219
75
  Matches = match(GroupMatcherM, *D->getBody(), AM.getASTContext());
220
75
  for (BoundNodes Match : Matches)
221
4
    emitDiagnostics(Match, "group", BR, ADC, this);
222
75
}
223
224
} // end of anonymous namespace
225
226
5
void ento::registerGCDAntipattern(CheckerManager &Mgr) {
227
5
  Mgr.registerChecker<GCDAntipatternChecker>();
228
5
}
229
230
5
bool ento::shouldRegisterGCDAntipattern(const LangOptions &LO) {
231
5
  return true;
232
5
}