Coverage Report

Created: 2019-07-24 05:18

/Users/buildslave/jenkins/workspace/clang-stage2-coverage-R/llvm/tools/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
42
69
42
    if (C == A)
70
2
      return true;
71
40
72
40
    if (C == B)
73
5
      return false;
74
35
75
35
    return seenBefore(C, A, B);
76
35
  }
77
45
  
return false3
;
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
10
86
10
  assert(D->hasBody());
87
10
  const Stmt *DeclBody = D->getBody();
88
10
89
10
  AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D);
90
10
91
10
  const auto *ME = Match.getNodeAs<ObjCMessageExpr>(OtherMsgBind);
92
10
  assert(ME);
93
10
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
10
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
10
105
10
  // Launch of run loop occurs before the message-sent expression is seen.
106
10
  if (seenBefore(DeclBody, RLR, ME))
107
2
    return;
108
8
109
8
  if (HasAutoreleasePool && 
(OAP != AP)5
)
110
1
    return;
111
7
112
7
  PathDiagnosticLocation Location = PathDiagnosticLocation::createBegin(
113
7
    ME, BR.getSourceManager(), ADC);
114
7
  SourceRange Range = ME->getSourceRange();
115
7
116
7
  BR.EmitBasicReport(ADC->getDecl(), Checker,
117
7
                     /*Name=*/"Memory leak inside autorelease pool",
118
7
                     /*BugCategory=*/"Memory",
119
7
                     /*Name=*/
120
7
                     (Twine("Temporary objects allocated in the") +
121
7
                      " autorelease pool " +
122
7
                      (HasAutoreleasePool ? 
""4
:
"of last resort "3
) +
123
7
                      "followed by the launch of " +
124
7
                      (RL ? 
"main run loop "5
:
"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
378
static StatementMatcher getRunLoopRunM(StatementMatcher Extra = anything()) {
132
378
  StatementMatcher MainRunLoopM =
133
378
      objcMessageExpr(hasSelector("mainRunLoop"),
134
378
                      hasReceiverType(asString("NSRunLoop")),
135
378
                      Extra)
136
378
          .bind(RunLoopBind);
137
378
138
378
  StatementMatcher MainRunLoopRunM = objcMessageExpr(hasSelector("run"),
139
378
                         hasReceiver(MainRunLoopM),
140
378
                         Extra).bind(RunLoopRunBind);
141
378
142
378
  StatementMatcher XPCRunM =
143
378
      callExpr(callee(functionDecl(hasName("xpc_main")))).bind(RunLoopRunBind);
144
378
  return anyOf(MainRunLoopRunM, XPCRunM);
145
378
}
146
147
378
static StatementMatcher getOtherMessageSentM(StatementMatcher Extra = anything()) {
148
378
  return objcMessageExpr(unless(anyOf(equalsBoundNode(RunLoopBind),
149
378
                                      equalsBoundNode(RunLoopRunBind))),
150
378
                         Extra)
151
378
      .bind(OtherMsgBind);
152
378
}
153
154
static void
155
checkTempObjectsInSamePool(const Decl *D, AnalysisManager &AM, BugReporter &BR,
156
189
                           const RunLoopAutoreleaseLeakChecker *Chkr) {
157
189
  StatementMatcher RunLoopRunM = getRunLoopRunM();
158
189
  StatementMatcher OtherMessageSentM = getOtherMessageSentM(
159
189
    hasAncestor(autoreleasePoolStmt().bind(OtherStmtAutoreleasePoolBind)));
160
189
161
189
  StatementMatcher RunLoopInAutorelease =
162
189
      autoreleasePoolStmt(
163
189
        hasDescendant(RunLoopRunM),
164
189
        hasDescendant(OtherMessageSentM)).bind(AutoreleasePoolBind);
165
189
166
189
  DeclarationMatcher GroupM = decl(hasDescendant(RunLoopInAutorelease));
167
189
168
189
  auto Matches = match(GroupM, *D, AM.getASTContext());
169
189
  for (BoundNodes Match : Matches)
170
6
    emitDiagnostics(Match, D, BR, AM, Chkr);
171
189
}
172
173
static void
174
checkTempObjectsInNoPool(const Decl *D, AnalysisManager &AM, BugReporter &BR,
175
189
                         const RunLoopAutoreleaseLeakChecker *Chkr) {
176
189
177
189
  auto NoPoolM = unless(hasAncestor(autoreleasePoolStmt()));
178
189
179
189
  StatementMatcher RunLoopRunM = getRunLoopRunM(NoPoolM);
180
189
  StatementMatcher OtherMessageSentM = getOtherMessageSentM(NoPoolM);
181
189
182
189
  DeclarationMatcher GroupM = functionDecl(
183
189
    isMain(),
184
189
    hasDescendant(RunLoopRunM),
185
189
    hasDescendant(OtherMessageSentM)
186
189
  );
187
189
188
189
  auto Matches = match(GroupM, *D, AM.getASTContext());
189
189
190
189
  for (BoundNodes Match : Matches)
191
4
    emitDiagnostics(Match, D, BR, AM, Chkr);
192
189
193
189
}
194
195
void RunLoopAutoreleaseLeakChecker::checkASTCodeBody(const Decl *D,
196
                        AnalysisManager &AM,
197
189
                        BugReporter &BR) const {
198
189
  checkTempObjectsInSamePool(D, AM, BR, this);
199
189
  checkTempObjectsInNoPool(D, AM, BR, this);
200
189
}
201
202
29
void ento::registerRunLoopAutoreleaseLeakChecker(CheckerManager &mgr) {
203
29
  mgr.registerChecker<RunLoopAutoreleaseLeakChecker>();
204
29
}
205
206
29
bool ento::shouldRegisterRunLoopAutoreleaseLeakChecker(const LangOptions &LO) {
207
29
  return true;
208
29
}