123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- //=- 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;
- }
|