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