/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/StaticAnalyzer/Checkers/RunLoopAutoreleaseLeakChecker.cpp
Line | Count | Source |
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 | 45 | static bool seenBefore(const Stmt *Parent, const Stmt *A, const Stmt *B) { |
66 | 46 | for (const Stmt *C : Parent->children()) { |
67 | 46 | if (!C) continue4 ; |
68 | | |
69 | 42 | if (C == A) |
70 | 2 | return true; |
71 | | |
72 | 40 | if (C == B) |
73 | 5 | return false; |
74 | | |
75 | 35 | return seenBefore(C, A, B); |
76 | 40 | } |
77 | 3 | return false; |
78 | 45 | } |
79 | | |
80 | | static void emitDiagnostics(BoundNodes &Match, |
81 | | const Decl *D, |
82 | | BugReporter &BR, |
83 | | AnalysisManager &AM, |
84 | 10 | const RunLoopAutoreleaseLeakChecker *Checker) { |
85 | | |
86 | 10 | assert(D->hasBody()); |
87 | 10 | const Stmt *DeclBody = D->getBody(); |
88 | | |
89 | 10 | AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D); |
90 | | |
91 | 10 | const auto *ME = Match.getNodeAs<ObjCMessageExpr>(OtherMsgBind); |
92 | 10 | assert(ME); |
93 | | |
94 | 10 | const auto *AP = |
95 | 10 | Match.getNodeAs<ObjCAutoreleasePoolStmt>(AutoreleasePoolBind); |
96 | 10 | const auto *OAP = |
97 | 10 | Match.getNodeAs<ObjCAutoreleasePoolStmt>(OtherStmtAutoreleasePoolBind); |
98 | 10 | bool HasAutoreleasePool = (AP != nullptr); |
99 | | |
100 | 10 | const auto *RL = Match.getNodeAs<ObjCMessageExpr>(RunLoopBind); |
101 | 10 | const auto *RLR = Match.getNodeAs<Stmt>(RunLoopRunBind); |
102 | 10 | assert(RLR && "Run loop launch not found"); |
103 | 10 | assert(ME != RLR); |
104 | | |
105 | | // Launch of run loop occurs before the message-sent expression is seen. |
106 | 10 | if (seenBefore(DeclBody, RLR, ME)) |
107 | 2 | return; |
108 | | |
109 | 8 | if (HasAutoreleasePool && (OAP != AP)5 ) |
110 | 1 | return; |
111 | | |
112 | 7 | PathDiagnosticLocation Location = PathDiagnosticLocation::createBegin( |
113 | 7 | ME, BR.getSourceManager(), ADC); |
114 | 7 | SourceRange Range = ME->getSourceRange(); |
115 | | |
116 | 7 | BR.EmitBasicReport(ADC->getDecl(), Checker, |
117 | 7 | /*Name=*/"Memory leak inside autorelease pool", |
118 | 7 | /*BugCategory=*/"Memory", |
119 | | /*Name=*/ |
120 | 7 | (Twine("Temporary objects allocated in the") + |
121 | 7 | " autorelease pool " + |
122 | 7 | (HasAutoreleasePool ? ""4 : "of last resort "3 ) + |
123 | 7 | "followed by the launch of " + |
124 | 7 | (RL ? "main run loop "5 : "xpc_main "2 ) + |
125 | 7 | "may never get released; consider moving them to a " |
126 | 7 | "separate autorelease pool") |
127 | 7 | .str(), |
128 | 7 | Location, Range); |
129 | 7 | } |
130 | | |
131 | 540 | static StatementMatcher getRunLoopRunM(StatementMatcher Extra = anything()) { |
132 | 540 | StatementMatcher MainRunLoopM = |
133 | 540 | objcMessageExpr(hasSelector("mainRunLoop"), |
134 | 540 | hasReceiverType(asString("NSRunLoop")), |
135 | 540 | Extra) |
136 | 540 | .bind(RunLoopBind); |
137 | | |
138 | 540 | StatementMatcher MainRunLoopRunM = objcMessageExpr(hasSelector("run"), |
139 | 540 | hasReceiver(MainRunLoopM), |
140 | 540 | Extra).bind(RunLoopRunBind); |
141 | | |
142 | 540 | StatementMatcher XPCRunM = |
143 | 540 | callExpr(callee(functionDecl(hasName("xpc_main")))).bind(RunLoopRunBind); |
144 | 540 | return anyOf(MainRunLoopRunM, XPCRunM); |
145 | 540 | } |
146 | | |
147 | 540 | static StatementMatcher getOtherMessageSentM(StatementMatcher Extra = anything()) { |
148 | 540 | return objcMessageExpr(unless(anyOf(equalsBoundNode(RunLoopBind), |
149 | 540 | equalsBoundNode(RunLoopRunBind))), |
150 | 540 | Extra) |
151 | 540 | .bind(OtherMsgBind); |
152 | 540 | } |
153 | | |
154 | | static void |
155 | | checkTempObjectsInSamePool(const Decl *D, AnalysisManager &AM, BugReporter &BR, |
156 | 270 | const RunLoopAutoreleaseLeakChecker *Chkr) { |
157 | 270 | StatementMatcher RunLoopRunM = getRunLoopRunM(); |
158 | 270 | StatementMatcher OtherMessageSentM = getOtherMessageSentM( |
159 | 270 | hasAncestor(autoreleasePoolStmt().bind(OtherStmtAutoreleasePoolBind))); |
160 | | |
161 | 270 | StatementMatcher RunLoopInAutorelease = |
162 | 270 | autoreleasePoolStmt( |
163 | 270 | hasDescendant(RunLoopRunM), |
164 | 270 | hasDescendant(OtherMessageSentM)).bind(AutoreleasePoolBind); |
165 | | |
166 | 270 | DeclarationMatcher GroupM = decl(hasDescendant(RunLoopInAutorelease)); |
167 | | |
168 | 270 | auto Matches = match(GroupM, *D, AM.getASTContext()); |
169 | 270 | for (BoundNodes Match : Matches) |
170 | 6 | emitDiagnostics(Match, D, BR, AM, Chkr); |
171 | 270 | } |
172 | | |
173 | | static void |
174 | | checkTempObjectsInNoPool(const Decl *D, AnalysisManager &AM, BugReporter &BR, |
175 | 270 | const RunLoopAutoreleaseLeakChecker *Chkr) { |
176 | | |
177 | 270 | auto NoPoolM = unless(hasAncestor(autoreleasePoolStmt())); |
178 | | |
179 | 270 | StatementMatcher RunLoopRunM = getRunLoopRunM(NoPoolM); |
180 | 270 | StatementMatcher OtherMessageSentM = getOtherMessageSentM(NoPoolM); |
181 | | |
182 | 270 | DeclarationMatcher GroupM = functionDecl( |
183 | 270 | isMain(), |
184 | 270 | hasDescendant(RunLoopRunM), |
185 | 270 | hasDescendant(OtherMessageSentM) |
186 | 270 | ); |
187 | | |
188 | 270 | auto Matches = match(GroupM, *D, AM.getASTContext()); |
189 | | |
190 | 270 | for (BoundNodes Match : Matches) |
191 | 4 | emitDiagnostics(Match, D, BR, AM, Chkr); |
192 | | |
193 | 270 | } |
194 | | |
195 | | void RunLoopAutoreleaseLeakChecker::checkASTCodeBody(const Decl *D, |
196 | | AnalysisManager &AM, |
197 | 270 | BugReporter &BR) const { |
198 | 270 | checkTempObjectsInSamePool(D, AM, BR, this); |
199 | 270 | checkTempObjectsInNoPool(D, AM, BR, this); |
200 | 270 | } |
201 | | |
202 | 53 | void ento::registerRunLoopAutoreleaseLeakChecker(CheckerManager &mgr) { |
203 | 53 | mgr.registerChecker<RunLoopAutoreleaseLeakChecker>(); |
204 | 53 | } |
205 | | |
206 | 106 | bool ento::shouldRegisterRunLoopAutoreleaseLeakChecker(const CheckerManager &mgr) { |
207 | 106 | return true; |
208 | 106 | } |