Coverage Report

Created: 2023-09-21 18:56

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