Coverage Report

Created: 2020-02-15 09:57

/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
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
402
static StatementMatcher getRunLoopRunM(StatementMatcher Extra = anything()) {
132
402
  StatementMatcher MainRunLoopM =
133
402
      objcMessageExpr(hasSelector("mainRunLoop"),
134
402
                      hasReceiverType(asString("NSRunLoop")),
135
402
                      Extra)
136
402
          .bind(RunLoopBind);
137
402
138
402
  StatementMatcher MainRunLoopRunM = objcMessageExpr(hasSelector("run"),
139
402
                         hasReceiver(MainRunLoopM),
140
402
                         Extra).bind(RunLoopRunBind);
141
402
142
402
  StatementMatcher XPCRunM =
143
402
      callExpr(callee(functionDecl(hasName("xpc_main")))).bind(RunLoopRunBind);
144
402
  return anyOf(MainRunLoopRunM, XPCRunM);
145
402
}
146
147
402
static StatementMatcher getOtherMessageSentM(StatementMatcher Extra = anything()) {
148
402
  return objcMessageExpr(unless(anyOf(equalsBoundNode(RunLoopBind),
149
402
                                      equalsBoundNode(RunLoopRunBind))),
150
402
                         Extra)
151
402
      .bind(OtherMsgBind);
152
402
}
153
154
static void
155
checkTempObjectsInSamePool(const Decl *D, AnalysisManager &AM, BugReporter &BR,
156
201
                           const RunLoopAutoreleaseLeakChecker *Chkr) {
157
201
  StatementMatcher RunLoopRunM = getRunLoopRunM();
158
201
  StatementMatcher OtherMessageSentM = getOtherMessageSentM(
159
201
    hasAncestor(autoreleasePoolStmt().bind(OtherStmtAutoreleasePoolBind)));
160
201
161
201
  StatementMatcher RunLoopInAutorelease =
162
201
      autoreleasePoolStmt(
163
201
        hasDescendant(RunLoopRunM),
164
201
        hasDescendant(OtherMessageSentM)).bind(AutoreleasePoolBind);
165
201
166
201
  DeclarationMatcher GroupM = decl(hasDescendant(RunLoopInAutorelease));
167
201
168
201
  auto Matches = match(GroupM, *D, AM.getASTContext());
169
201
  for (BoundNodes Match : Matches)
170
6
    emitDiagnostics(Match, D, BR, AM, Chkr);
171
201
}
172
173
static void
174
checkTempObjectsInNoPool(const Decl *D, AnalysisManager &AM, BugReporter &BR,
175
201
                         const RunLoopAutoreleaseLeakChecker *Chkr) {
176
201
177
201
  auto NoPoolM = unless(hasAncestor(autoreleasePoolStmt()));
178
201
179
201
  StatementMatcher RunLoopRunM = getRunLoopRunM(NoPoolM);
180
201
  StatementMatcher OtherMessageSentM = getOtherMessageSentM(NoPoolM);
181
201
182
201
  DeclarationMatcher GroupM = functionDecl(
183
201
    isMain(),
184
201
    hasDescendant(RunLoopRunM),
185
201
    hasDescendant(OtherMessageSentM)
186
201
  );
187
201
188
201
  auto Matches = match(GroupM, *D, AM.getASTContext());
189
201
190
201
  for (BoundNodes Match : Matches)
191
4
    emitDiagnostics(Match, D, BR, AM, Chkr);
192
201
193
201
}
194
195
void RunLoopAutoreleaseLeakChecker::checkASTCodeBody(const Decl *D,
196
                        AnalysisManager &AM,
197
201
                        BugReporter &BR) const {
198
201
  checkTempObjectsInSamePool(D, AM, BR, this);
199
201
  checkTempObjectsInNoPool(D, AM, BR, this);
200
201
}
201
202
38
void ento::registerRunLoopAutoreleaseLeakChecker(CheckerManager &mgr) {
203
38
  mgr.registerChecker<RunLoopAutoreleaseLeakChecker>();
204
38
}
205
206
38
bool ento::shouldRegisterRunLoopAutoreleaseLeakChecker(const LangOptions &LO) {
207
38
  return true;
208
38
}