Coverage Report

Created: 2020-09-22 08:39

/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/StaticAnalyzer/Checkers/RunLoopAutoreleaseLeakChecker.cpp
Line
Count
Source
1
//=- RunLoopAutoreleaseLeakChecker.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
//
10
// A checker for detecting leaks resulting from allocating temporary
11
// autoreleased objects before starting the main run loop.
12
//
13
// Checks for two antipatterns:
14
// 1. ObjCMessageExpr followed by [[NSRunLoop mainRunLoop] run] in the same
15
// autorelease pool.
16
// 2. ObjCMessageExpr followed by [[NSRunLoop mainRunLoop] run] in no
17
// autorelease pool.
18
//
19
// Any temporary objects autoreleased in code called in those expressions
20
// will not be deallocated until the program exits, and are effectively leaks.
21
//
22
//===----------------------------------------------------------------------===//
23
//
24
25
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
26
#include "clang/AST/Decl.h"
27
#include "clang/AST/DeclObjC.h"
28
#include "clang/ASTMatchers/ASTMatchFinder.h"
29
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
30
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
31
#include "clang/StaticAnalyzer/Core/Checker.h"
32
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
33
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
34
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
35
#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h"
36
37
using namespace clang;
38
using namespace ento;
39
using namespace ast_matchers;
40
41
namespace {
42
43
const char * RunLoopBind = "NSRunLoopM";
44
const char * RunLoopRunBind = "RunLoopRunM";
45
const char * OtherMsgBind = "OtherMessageSentM";
46
const char * AutoreleasePoolBind = "AutoreleasePoolM";
47
const char * OtherStmtAutoreleasePoolBind = "OtherAutoreleasePoolM";
48
49
class RunLoopAutoreleaseLeakChecker : public Checker<check::ASTCodeBody> {
50
51
public:
52
  void checkASTCodeBody(const Decl *D,
53
                        AnalysisManager &AM,
54
                        BugReporter &BR) const;
55
56
};
57
58
} // end anonymous namespace
59
60
/// \return Whether {@code A} occurs before {@code B} in traversal of
61
/// {@code Parent}.
62
/// Conceptually a very incomplete/unsound approximation of happens-before
63
/// relationship (A is likely to be evaluated before B),
64
/// but useful enough in this case.
65
45
static bool seenBefore(const Stmt *Parent, const Stmt *A, const Stmt *B) {
66
46
  for (const Stmt *C : Parent->children()) {
67
46
    if (!C) 
continue4
;
68
69
42
    if (C == A)
70
2
      return true;
71
72
40
    if (C == B)
73
5
      return false;
74
75
35
    return seenBefore(C, A, B);
76
35
  }
77
3
  return false;
78
45
}
79
80
static void emitDiagnostics(BoundNodes &Match,
81
                            const Decl *D,
82
                            BugReporter &BR,
83
                            AnalysisManager &AM,
84
10
                            const RunLoopAutoreleaseLeakChecker *Checker) {
85
86
10
  assert(D->hasBody());
87
10
  const Stmt *DeclBody = D->getBody();
88
89
10
  AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);
90
91
10
  const auto *ME = Match.getNodeAs<ObjCMessageExpr>(OtherMsgBind);
92
10
  assert(ME);
93
94
10
  const auto *AP =
95
10
      Match.getNodeAs<ObjCAutoreleasePoolStmt>(AutoreleasePoolBind);
96
10
  const auto *OAP =
97
10
      Match.getNodeAs<ObjCAutoreleasePoolStmt>(OtherStmtAutoreleasePoolBind);
98
10
  bool HasAutoreleasePool = (AP != nullptr);
99
100
10
  const auto *RL = Match.getNodeAs<ObjCMessageExpr>(RunLoopBind);
101
10
  const auto *RLR = Match.getNodeAs<Stmt>(RunLoopRunBind);
102
10
  assert(RLR && "Run loop launch not found");
103
10
  assert(ME != RLR);
104
105
  // Launch of run loop occurs before the message-sent expression is seen.
106
10
  if (seenBefore(DeclBody, RLR, ME))
107
2
    return;
108
109
8
  if (HasAutoreleasePool && 
(OAP != AP)5
)
110
1
    return;
111
112
7
  PathDiagnosticLocation Location = PathDiagnosticLocation::createBegin(
113
7
    ME, BR.getSourceManager(), ADC);
114
7
  SourceRange Range = ME->getSourceRange();
115
116
7
  BR.EmitBasicReport(ADC->getDecl(), Checker,
117
7
                     /*Name=*/"Memory leak inside autorelease pool",
118
7
                     /*BugCategory=*/"Memory",
119
                     /*Name=*/
120
7
                     (Twine("Temporary objects allocated in the") +
121
7
                      " autorelease pool " +
122
4
                      (HasAutoreleasePool ? "" : 
"of last resort "3
) +
123
7
                      "followed by the launch of " +
124
5
                      (RL ? "main run loop " : 
"xpc_main "2
) +
125
7
                      "may never get released; consider moving them to a "
126
7
                      "separate autorelease pool")
127
7
                         .str(),
128
7
                     Location, Range);
129
7
}
130
131
432
static StatementMatcher getRunLoopRunM(StatementMatcher Extra = anything()) {
132
432
  StatementMatcher MainRunLoopM =
133
432
      objcMessageExpr(hasSelector("mainRunLoop"),
134
432
                      hasReceiverType(asString("NSRunLoop")),
135
432
                      Extra)
136
432
          .bind(RunLoopBind);
137
138
432
  StatementMatcher MainRunLoopRunM = objcMessageExpr(hasSelector("run"),
139
432
                         hasReceiver(MainRunLoopM),
140
432
                         Extra).bind(RunLoopRunBind);
141
142
432
  StatementMatcher XPCRunM =
143
432
      callExpr(callee(functionDecl(hasName("xpc_main")))).bind(RunLoopRunBind);
144
432
  return anyOf(MainRunLoopRunM, XPCRunM);
145
432
}
146
147
432
static StatementMatcher getOtherMessageSentM(StatementMatcher Extra = anything()) {
148
432
  return objcMessageExpr(unless(anyOf(equalsBoundNode(RunLoopBind),
149
432
                                      equalsBoundNode(RunLoopRunBind))),
150
432
                         Extra)
151
432
      .bind(OtherMsgBind);
152
432
}
153
154
static void
155
checkTempObjectsInSamePool(const Decl *D, AnalysisManager &AM, BugReporter &BR,
156
216
                           const RunLoopAutoreleaseLeakChecker *Chkr) {
157
216
  StatementMatcher RunLoopRunM = getRunLoopRunM();
158
216
  StatementMatcher OtherMessageSentM = getOtherMessageSentM(
159
216
    hasAncestor(autoreleasePoolStmt().bind(OtherStmtAutoreleasePoolBind)));
160
161
216
  StatementMatcher RunLoopInAutorelease =
162
216
      autoreleasePoolStmt(
163
216
        hasDescendant(RunLoopRunM),
164
216
        hasDescendant(OtherMessageSentM)).bind(AutoreleasePoolBind);
165
166
216
  DeclarationMatcher GroupM = decl(hasDescendant(RunLoopInAutorelease));
167
168
216
  auto Matches = match(GroupM, *D, AM.getASTContext());
169
216
  for (BoundNodes Match : Matches)
170
6
    emitDiagnostics(Match, D, BR, AM, Chkr);
171
216
}
172
173
static void
174
checkTempObjectsInNoPool(const Decl *D, AnalysisManager &AM, BugReporter &BR,
175
216
                         const RunLoopAutoreleaseLeakChecker *Chkr) {
176
177
216
  auto NoPoolM = unless(hasAncestor(autoreleasePoolStmt()));
178
179
216
  StatementMatcher RunLoopRunM = getRunLoopRunM(NoPoolM);
180
216
  StatementMatcher OtherMessageSentM = getOtherMessageSentM(NoPoolM);
181
182
216
  DeclarationMatcher GroupM = functionDecl(
183
216
    isMain(),
184
216
    hasDescendant(RunLoopRunM),
185
216
    hasDescendant(OtherMessageSentM)
186
216
  );
187
188
216
  auto Matches = match(GroupM, *D, AM.getASTContext());
189
190
216
  for (BoundNodes Match : Matches)
191
4
    emitDiagnostics(Match, D, BR, AM, Chkr);
192
193
216
}
194
195
void RunLoopAutoreleaseLeakChecker::checkASTCodeBody(const Decl *D,
196
                        AnalysisManager &AM,
197
216
                        BugReporter &BR) const {
198
216
  checkTempObjectsInSamePool(D, AM, BR, this);
199
216
  checkTempObjectsInNoPool(D, AM, BR, this);
200
216
}
201
202
43
void ento::registerRunLoopAutoreleaseLeakChecker(CheckerManager &mgr) {
203
43
  mgr.registerChecker<RunLoopAutoreleaseLeakChecker>();
204
43
}
205
206
86
bool ento::shouldRegisterRunLoopAutoreleaseLeakChecker(const CheckerManager &mgr) {
207
86
  return true;
208
86
}