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 @c A occurs before @c B in traversal of |
61 | /// @c 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(Parent: 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>(ID: OtherMsgBind); |
92 | assert(ME); |
93 | |
94 | const auto *AP = |
95 | Match.getNodeAs<ObjCAutoreleasePoolStmt>(ID: AutoreleasePoolBind); |
96 | const auto *OAP = |
97 | Match.getNodeAs<ObjCAutoreleasePoolStmt>(ID: OtherStmtAutoreleasePoolBind); |
98 | bool HasAutoreleasePool = (AP != nullptr); |
99 | |
100 | const auto *RL = Match.getNodeAs<ObjCMessageExpr>(ID: RunLoopBind); |
101 | const auto *RLR = Match.getNodeAs<Stmt>(ID: 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(Parent: DeclBody, A: RLR, B: ME)) |
107 | return; |
108 | |
109 | if (HasAutoreleasePool && (OAP != AP)) |
110 | return; |
111 | |
112 | PathDiagnosticLocation Location = PathDiagnosticLocation::createBegin( |
113 | S: ME, SM: BR.getSourceManager(), LAC: ADC); |
114 | SourceRange Range = ME->getSourceRange(); |
115 | |
116 | BR.EmitBasicReport(DeclWithIssue: ADC->getDecl(), Checker, |
117 | /*Name=*/BugName: "Memory leak inside autorelease pool" , |
118 | /*BugCategory=*/"Memory" , |
119 | /*Name=*/ |
120 | BugStr: (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 | Loc: Location, Ranges: Range); |
129 | } |
130 | |
131 | static StatementMatcher getRunLoopRunM(StatementMatcher = anything()) { |
132 | StatementMatcher MainRunLoopM = |
133 | objcMessageExpr(hasSelector(BaseName: "mainRunLoop" ), |
134 | hasReceiverType(InnerMatcher: asString(Name: "NSRunLoop" )), |
135 | Extra) |
136 | .bind(ID: RunLoopBind); |
137 | |
138 | StatementMatcher MainRunLoopRunM = objcMessageExpr(hasSelector(BaseName: "run" ), |
139 | hasReceiver(InnerMatcher: MainRunLoopM), |
140 | Extra).bind(ID: RunLoopRunBind); |
141 | |
142 | StatementMatcher XPCRunM = |
143 | callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "xpc_main" )))).bind(ID: RunLoopRunBind); |
144 | return anyOf(MainRunLoopRunM, XPCRunM); |
145 | } |
146 | |
147 | static StatementMatcher getOtherMessageSentM(StatementMatcher = anything()) { |
148 | return objcMessageExpr(unless(anyOf(equalsBoundNode(ID: RunLoopBind), |
149 | equalsBoundNode(ID: RunLoopRunBind))), |
150 | Extra) |
151 | .bind(ID: 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 | Extra: hasAncestor(autoreleasePoolStmt().bind(ID: OtherStmtAutoreleasePoolBind))); |
160 | |
161 | StatementMatcher RunLoopInAutorelease = |
162 | autoreleasePoolStmt( |
163 | hasDescendant(RunLoopRunM), |
164 | hasDescendant(OtherMessageSentM)).bind(ID: AutoreleasePoolBind); |
165 | |
166 | DeclarationMatcher GroupM = decl(hasDescendant(RunLoopInAutorelease)); |
167 | |
168 | auto Matches = match(Matcher: GroupM, Node: *D, Context&: AM.getASTContext()); |
169 | for (BoundNodes Match : Matches) |
170 | emitDiagnostics(Match, D, BR, AM, Checker: 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(Extra: NoPoolM); |
180 | StatementMatcher OtherMessageSentM = getOtherMessageSentM(Extra: NoPoolM); |
181 | |
182 | DeclarationMatcher GroupM = functionDecl( |
183 | isMain(), |
184 | hasDescendant(RunLoopRunM), |
185 | hasDescendant(OtherMessageSentM) |
186 | ); |
187 | |
188 | auto Matches = match(Matcher: GroupM, Node: *D, Context&: AM.getASTContext()); |
189 | |
190 | for (BoundNodes Match : Matches) |
191 | emitDiagnostics(Match, D, BR, AM, Checker: Chkr); |
192 | |
193 | } |
194 | |
195 | void RunLoopAutoreleaseLeakChecker::checkASTCodeBody(const Decl *D, |
196 | AnalysisManager &AM, |
197 | BugReporter &BR) const { |
198 | checkTempObjectsInSamePool(D, AM, BR, Chkr: this); |
199 | checkTempObjectsInNoPool(D, AM, BR, Chkr: 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 | |