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 37 using namespace clang; 38 using namespace ento; 39 using namespace ast_matchers; 40 41 namespace { 42 43 const char * RunLoopBind = "NSRunLoopM"; 44 const char * RunLoopRunBind = "RunLoopRunM"; 45 const char * OtherMsgBind = "OtherMessageSentM"; 46 const char * AutoreleasePoolBind = "AutoreleasePoolM"; 47 const char * OtherStmtAutoreleasePoolBind = "OtherAutoreleasePoolM"; 48 49 class RunLoopAutoreleaseLeakChecker : public Checker<check::ASTCodeBody> { 50 51 public: 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. 65 static 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 80 static 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 131 static 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 147 static StatementMatcher getOtherMessageSentM(StatementMatcher Extra = anything()) { 148 return objcMessageExpr(unless(anyOf(equalsBoundNode(RunLoopBind), 149 equalsBoundNode(RunLoopRunBind))), 150 Extra) 151 .bind(OtherMsgBind); 152 } 153 154 static void 155 checkTempObjectsInSamePool(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 173 static void 174 checkTempObjectsInNoPool(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 195 void 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 202 void ento::registerRunLoopAutoreleaseLeakChecker(CheckerManager &mgr) { 203 mgr.registerChecker<RunLoopAutoreleaseLeakChecker>(); 204 } 205 206 bool ento::shouldRegisterRunLoopAutoreleaseLeakChecker(const CheckerManager &mgr) { 207 return true; 208 } 209