Coverage Report

Created: 2023-09-30 09:22

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