209 lines
7.2 KiB
C++
209 lines
7.2 KiB
C++
|
//=- 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 {@code A} occurs before {@code B} in traversal of
|
||
|
/// {@code 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;
|
||
|
}
|