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 49 class RunLoopAutoreleaseLeakChecker : public Checker< 50 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 == A) 70 return true; 71 72 if (C == B) 73 return false; 74 75 Optional<TriBoolTy> &Cached = Memoization[C]; 76 if (!Cached) 77 Cached = seenBeforeRec(C, A, B, Memoization); 78 79 if (Cached->hasValue()) 80 return Cached->getValue(); 81 } 82 83 return None; 84 } 85 86 /// \return Whether {@code A} occurs before {@code B} in traversal of 87 /// {@code Parent}. 88 /// Conceptually a very incomplete/unsound approximation of happens-before 89 /// relationship (A is likely to be evaluated before B), 90 /// but useful enough in this case. 91 static bool seenBefore(const Stmt *Parent, const Stmt *A, const Stmt *B) { 92 MemoizationMapTy Memoization; 93 TriBoolTy Val = seenBeforeRec(Parent, A, B, Memoization); 94 return Val.getValue(); 95 } 96 97 static void emitDiagnostics(BoundNodes &Match, 98 const Decl *D, 99 BugReporter &BR, 100 AnalysisManager &AM, 101 const RunLoopAutoreleaseLeakChecker *Checker) { 102 103 assert(D->hasBody()); 104 const Stmt *DeclBody = D->getBody(); 105 106 AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D); 107 108 const auto *ME = Match.getNodeAs<ObjCMessageExpr>(OtherMsgBind); 109 assert(ME); 110 111 const auto *AP = 112 Match.getNodeAs<ObjCAutoreleasePoolStmt>(AutoreleasePoolBind); 113 bool HasAutoreleasePool = (AP != nullptr); 114 115 const auto *RL = Match.getNodeAs<ObjCMessageExpr>(RunLoopBind); 116 const auto *RLR = Match.getNodeAs<Stmt>(RunLoopRunBind); 117 assert(RLR && "Run loop launch not found"); 118 119 assert(ME != RLR); 120 if (HasAutoreleasePool && seenBefore(AP, RLR, ME)) 121 return; 122 123 if (!HasAutoreleasePool && seenBefore(DeclBody, RLR, ME)) 124 return; 125 126 PathDiagnosticLocation Location = PathDiagnosticLocation::createBegin( 127 ME, BR.getSourceManager(), ADC); 128 SourceRange Range = ME->getSourceRange(); 129 130 BR.EmitBasicReport(ADC->getDecl(), Checker, 131 /*Name=*/"Memory leak inside autorelease pool", 132 /*Category=*/"Memory", 133 /*Name=*/ 134 (Twine("Temporary objects allocated in the") + 135 " autorelease pool " + 136 (HasAutoreleasePool ? "" : "of last resort ") + 137 "followed by the launch of " + 138 (RL ? "main run loop " : "xpc_main ") + 139 "may never get released; consider moving them to a " 140 "separate autorelease pool") 141 .str(), 142 Location, Range); 143 } 144 145 static StatementMatcher getRunLoopRunM(StatementMatcher Extra = anything()) { 146 StatementMatcher MainRunLoopM = 147 objcMessageExpr(hasSelector("mainRunLoop"), 148 hasReceiverType(asString("NSRunLoop")), 149 Extra) 150 .bind(RunLoopBind); 151 152 StatementMatcher MainRunLoopRunM = objcMessageExpr(hasSelector("run"), 153 hasReceiver(MainRunLoopM), 154 Extra).bind(RunLoopRunBind); 155 156 StatementMatcher XPCRunM = 157 callExpr(callee(functionDecl(hasName("xpc_main")))).bind(RunLoopRunBind); 158 return anyOf(MainRunLoopRunM, XPCRunM); 159 } 160 161 static StatementMatcher getOtherMessageSentM(StatementMatcher Extra = anything()) { 162 return objcMessageExpr(unless(anyOf(equalsBoundNode(RunLoopBind), 163 equalsBoundNode(RunLoopRunBind))), 164 Extra) 165 .bind(OtherMsgBind); 166 } 167 168 static void 169 checkTempObjectsInSamePool(const Decl *D, AnalysisManager &AM, BugReporter &BR, 170 const RunLoopAutoreleaseLeakChecker *Chkr) { 171 StatementMatcher RunLoopRunM = getRunLoopRunM(); 172 StatementMatcher OtherMessageSentM = getOtherMessageSentM(); 173 174 StatementMatcher RunLoopInAutorelease = 175 autoreleasePoolStmt( 176 hasDescendant(RunLoopRunM), 177 hasDescendant(OtherMessageSentM)).bind(AutoreleasePoolBind); 178 179 DeclarationMatcher GroupM = decl(hasDescendant(RunLoopInAutorelease)); 180 181 auto Matches = match(GroupM, *D, AM.getASTContext()); 182 for (BoundNodes Match : Matches) 183 emitDiagnostics(Match, D, BR, AM, Chkr); 184 } 185 186 static void 187 checkTempObjectsInNoPool(const Decl *D, AnalysisManager &AM, BugReporter &BR, 188 const RunLoopAutoreleaseLeakChecker *Chkr) { 189 190 auto NoPoolM = unless(hasAncestor(autoreleasePoolStmt())); 191 192 StatementMatcher RunLoopRunM = getRunLoopRunM(NoPoolM); 193 StatementMatcher OtherMessageSentM = getOtherMessageSentM(NoPoolM); 194 195 DeclarationMatcher GroupM = functionDecl( 196 isMain(), 197 hasDescendant(RunLoopRunM), 198 hasDescendant(OtherMessageSentM) 199 ); 200 201 auto Matches = match(GroupM, *D, AM.getASTContext()); 202 203 for (BoundNodes Match : Matches) 204 emitDiagnostics(Match, D, BR, AM, Chkr); 205 206 } 207 208 void RunLoopAutoreleaseLeakChecker::checkASTCodeBody(const Decl *D, 209 AnalysisManager &AM, 210 BugReporter &BR) const { 211 checkTempObjectsInSamePool(D, AM, BR, this); 212 checkTempObjectsInNoPool(D, AM, BR, this); 213 } 214 215 void ento::registerRunLoopAutoreleaseLeakChecker(CheckerManager &mgr) { 216 mgr.registerChecker<RunLoopAutoreleaseLeakChecker>(); 217 } 218