1 | //===- ObjCSuperDeallocChecker.cpp - Check correct use of [super dealloc] -===// |
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 | // This defines ObjCSuperDeallocChecker, a builtin check that warns when |
10 | // self is used after a call to [super dealloc] in MRR mode. |
11 | // |
12 | //===----------------------------------------------------------------------===// |
13 | |
14 | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
15 | #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" |
16 | #include "clang/StaticAnalyzer/Core/Checker.h" |
17 | #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" |
18 | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
19 | #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" |
20 | #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" |
21 | |
22 | using namespace clang; |
23 | using namespace ento; |
24 | |
25 | namespace { |
26 | class ObjCSuperDeallocChecker |
27 | : public Checker<check::PostObjCMessage, check::PreObjCMessage, |
28 | check::PreCall, check::Location> { |
29 | mutable const IdentifierInfo *IIdealloc = nullptr; |
30 | mutable const IdentifierInfo *IINSObject = nullptr; |
31 | mutable Selector SELdealloc; |
32 | |
33 | const BugType DoubleSuperDeallocBugType{ |
34 | this, "[super dealloc] should not be called more than once" , |
35 | categories::CoreFoundationObjectiveC}; |
36 | |
37 | void initIdentifierInfoAndSelectors(ASTContext &Ctx) const; |
38 | |
39 | bool isSuperDeallocMessage(const ObjCMethodCall &M) const; |
40 | |
41 | public: |
42 | void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const; |
43 | void checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const; |
44 | |
45 | void checkPreCall(const CallEvent &Call, CheckerContext &C) const; |
46 | |
47 | void checkLocation(SVal l, bool isLoad, const Stmt *S, |
48 | CheckerContext &C) const; |
49 | |
50 | private: |
51 | |
52 | void diagnoseCallArguments(const CallEvent &CE, CheckerContext &C) const; |
53 | |
54 | void reportUseAfterDealloc(SymbolRef Sym, StringRef Desc, const Stmt *S, |
55 | CheckerContext &C) const; |
56 | }; |
57 | |
58 | } // End anonymous namespace. |
59 | |
60 | // Remember whether [super dealloc] has previously been called on the |
61 | // SymbolRef for the receiver. |
62 | REGISTER_SET_WITH_PROGRAMSTATE(CalledSuperDealloc, SymbolRef) |
63 | |
64 | namespace { |
65 | class SuperDeallocBRVisitor final : public BugReporterVisitor { |
66 | SymbolRef ReceiverSymbol; |
67 | bool Satisfied; |
68 | |
69 | public: |
70 | SuperDeallocBRVisitor(SymbolRef ReceiverSymbol) |
71 | : ReceiverSymbol(ReceiverSymbol), Satisfied(false) {} |
72 | |
73 | PathDiagnosticPieceRef VisitNode(const ExplodedNode *Succ, |
74 | BugReporterContext &BRC, |
75 | PathSensitiveBugReport &BR) override; |
76 | |
77 | void Profile(llvm::FoldingSetNodeID &ID) const override { |
78 | ID.Add(x: ReceiverSymbol); |
79 | } |
80 | }; |
81 | } // End anonymous namespace. |
82 | |
83 | void ObjCSuperDeallocChecker::checkPreObjCMessage(const ObjCMethodCall &M, |
84 | CheckerContext &C) const { |
85 | |
86 | ProgramStateRef State = C.getState(); |
87 | SymbolRef ReceiverSymbol = M.getReceiverSVal().getAsSymbol(); |
88 | if (!ReceiverSymbol) { |
89 | diagnoseCallArguments(CE: M, C); |
90 | return; |
91 | } |
92 | |
93 | bool AlreadyCalled = State->contains<CalledSuperDealloc>(key: ReceiverSymbol); |
94 | if (!AlreadyCalled) |
95 | return; |
96 | |
97 | StringRef Desc; |
98 | |
99 | if (isSuperDeallocMessage(M)) { |
100 | Desc = "[super dealloc] should not be called multiple times" ; |
101 | } else { |
102 | Desc = StringRef(); |
103 | } |
104 | |
105 | reportUseAfterDealloc(Sym: ReceiverSymbol, Desc, S: M.getOriginExpr(), C); |
106 | } |
107 | |
108 | void ObjCSuperDeallocChecker::checkPreCall(const CallEvent &Call, |
109 | CheckerContext &C) const { |
110 | diagnoseCallArguments(CE: Call, C); |
111 | } |
112 | |
113 | void ObjCSuperDeallocChecker::checkPostObjCMessage(const ObjCMethodCall &M, |
114 | CheckerContext &C) const { |
115 | // Check for [super dealloc] method call. |
116 | if (!isSuperDeallocMessage(M)) |
117 | return; |
118 | |
119 | ProgramStateRef State = C.getState(); |
120 | const LocationContext *LC = C.getLocationContext(); |
121 | SymbolRef SelfSymbol = State->getSelfSVal(LC).getAsSymbol(); |
122 | assert(SelfSymbol && "No receiver symbol at call to [super dealloc]?" ); |
123 | |
124 | // We add this transition in checkPostObjCMessage to avoid warning when |
125 | // we inline a call to [super dealloc] where the inlined call itself |
126 | // calls [super dealloc]. |
127 | State = State->add<CalledSuperDealloc>(K: SelfSymbol); |
128 | C.addTransition(State); |
129 | } |
130 | |
131 | void ObjCSuperDeallocChecker::checkLocation(SVal L, bool IsLoad, const Stmt *S, |
132 | CheckerContext &C) const { |
133 | SymbolRef BaseSym = L.getLocSymbolInBase(); |
134 | if (!BaseSym) |
135 | return; |
136 | |
137 | ProgramStateRef State = C.getState(); |
138 | |
139 | if (!State->contains<CalledSuperDealloc>(key: BaseSym)) |
140 | return; |
141 | |
142 | const MemRegion *R = L.getAsRegion(); |
143 | if (!R) |
144 | return; |
145 | |
146 | // Climb the super regions to find the base symbol while recording |
147 | // the second-to-last region for error reporting. |
148 | const MemRegion *PriorSubRegion = nullptr; |
149 | while (const SubRegion *SR = dyn_cast<SubRegion>(Val: R)) { |
150 | if (const SymbolicRegion *SymR = dyn_cast<SymbolicRegion>(Val: SR)) { |
151 | BaseSym = SymR->getSymbol(); |
152 | break; |
153 | } else { |
154 | R = SR->getSuperRegion(); |
155 | PriorSubRegion = SR; |
156 | } |
157 | } |
158 | |
159 | StringRef Desc = StringRef(); |
160 | auto *IvarRegion = dyn_cast_or_null<ObjCIvarRegion>(Val: PriorSubRegion); |
161 | |
162 | std::string Buf; |
163 | llvm::raw_string_ostream OS(Buf); |
164 | if (IvarRegion) { |
165 | OS << "Use of instance variable '" << *IvarRegion->getDecl() << |
166 | "' after 'self' has been deallocated" ; |
167 | Desc = OS.str(); |
168 | } |
169 | |
170 | reportUseAfterDealloc(Sym: BaseSym, Desc, S, C); |
171 | } |
172 | |
173 | /// Report a use-after-dealloc on Sym. If not empty, |
174 | /// Desc will be used to describe the error; otherwise, |
175 | /// a default warning will be used. |
176 | void ObjCSuperDeallocChecker::reportUseAfterDealloc(SymbolRef Sym, |
177 | StringRef Desc, |
178 | const Stmt *S, |
179 | CheckerContext &C) const { |
180 | // We have a use of self after free. |
181 | // This likely causes a crash, so stop exploring the |
182 | // path by generating a sink. |
183 | ExplodedNode *ErrNode = C.generateErrorNode(); |
184 | // If we've already reached this node on another path, return. |
185 | if (!ErrNode) |
186 | return; |
187 | |
188 | if (Desc.empty()) |
189 | Desc = "Use of 'self' after it has been deallocated" ; |
190 | |
191 | // Generate the report. |
192 | auto BR = std::make_unique<PathSensitiveBugReport>(args: DoubleSuperDeallocBugType, |
193 | args&: Desc, args&: ErrNode); |
194 | BR->addRange(R: S->getSourceRange()); |
195 | BR->addVisitor(visitor: std::make_unique<SuperDeallocBRVisitor>(args&: Sym)); |
196 | C.emitReport(R: std::move(BR)); |
197 | } |
198 | |
199 | /// Diagnose if any of the arguments to CE have already been |
200 | /// dealloc'd. |
201 | void ObjCSuperDeallocChecker::diagnoseCallArguments(const CallEvent &CE, |
202 | CheckerContext &C) const { |
203 | ProgramStateRef State = C.getState(); |
204 | unsigned ArgCount = CE.getNumArgs(); |
205 | for (unsigned I = 0; I < ArgCount; I++) { |
206 | SymbolRef Sym = CE.getArgSVal(Index: I).getAsSymbol(); |
207 | if (!Sym) |
208 | continue; |
209 | |
210 | if (State->contains<CalledSuperDealloc>(key: Sym)) { |
211 | reportUseAfterDealloc(Sym, Desc: StringRef(), S: CE.getArgExpr(Index: I), C); |
212 | return; |
213 | } |
214 | } |
215 | } |
216 | |
217 | void |
218 | ObjCSuperDeallocChecker::initIdentifierInfoAndSelectors(ASTContext &Ctx) const { |
219 | if (IIdealloc) |
220 | return; |
221 | |
222 | IIdealloc = &Ctx.Idents.get(Name: "dealloc" ); |
223 | IINSObject = &Ctx.Idents.get(Name: "NSObject" ); |
224 | |
225 | SELdealloc = Ctx.Selectors.getSelector(NumArgs: 0, IIV: &IIdealloc); |
226 | } |
227 | |
228 | bool |
229 | ObjCSuperDeallocChecker::isSuperDeallocMessage(const ObjCMethodCall &M) const { |
230 | if (M.getOriginExpr()->getReceiverKind() != ObjCMessageExpr::SuperInstance) |
231 | return false; |
232 | |
233 | ASTContext &Ctx = M.getState()->getStateManager().getContext(); |
234 | initIdentifierInfoAndSelectors(Ctx); |
235 | |
236 | return M.getSelector() == SELdealloc; |
237 | } |
238 | |
239 | PathDiagnosticPieceRef |
240 | SuperDeallocBRVisitor::VisitNode(const ExplodedNode *Succ, |
241 | BugReporterContext &BRC, |
242 | PathSensitiveBugReport &) { |
243 | if (Satisfied) |
244 | return nullptr; |
245 | |
246 | ProgramStateRef State = Succ->getState(); |
247 | |
248 | bool CalledNow = |
249 | Succ->getState()->contains<CalledSuperDealloc>(key: ReceiverSymbol); |
250 | bool CalledBefore = |
251 | Succ->getFirstPred()->getState()->contains<CalledSuperDealloc>( |
252 | key: ReceiverSymbol); |
253 | |
254 | // Is Succ the node on which the analyzer noted that [super dealloc] was |
255 | // called on ReceiverSymbol? |
256 | if (CalledNow && !CalledBefore) { |
257 | Satisfied = true; |
258 | |
259 | ProgramPoint P = Succ->getLocation(); |
260 | PathDiagnosticLocation L = |
261 | PathDiagnosticLocation::create(P, SMng: BRC.getSourceManager()); |
262 | |
263 | if (!L.isValid() || !L.asLocation().isValid()) |
264 | return nullptr; |
265 | |
266 | return std::make_shared<PathDiagnosticEventPiece>( |
267 | args&: L, args: "[super dealloc] called here" ); |
268 | } |
269 | |
270 | return nullptr; |
271 | } |
272 | |
273 | //===----------------------------------------------------------------------===// |
274 | // Checker Registration. |
275 | //===----------------------------------------------------------------------===// |
276 | |
277 | void ento::registerObjCSuperDeallocChecker(CheckerManager &Mgr) { |
278 | Mgr.registerChecker<ObjCSuperDeallocChecker>(); |
279 | } |
280 | |
281 | bool ento::shouldRegisterObjCSuperDeallocChecker(const CheckerManager &mgr) { |
282 | return true; |
283 | } |
284 | |