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