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