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 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 namespace 59 60/// \return Whether {@code A} occurs before {@code B} in traversal of 61/// {@code 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. 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 void 155checkTempObjectsInSamePool(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 void 174checkTempObjectsInNoPool(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