1 | //===- IvarInvalidationChecker.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 | // This checker implements annotation driven invalidation checking. If a class |
10 | // contains a method annotated with 'objc_instance_variable_invalidator', |
11 | // - (void) foo |
12 | // __attribute__((annotate("objc_instance_variable_invalidator"))); |
13 | // all the "ivalidatable" instance variables of this class should be |
14 | // invalidated. We call an instance variable ivalidatable if it is an object of |
15 | // a class which contains an invalidation method. There could be multiple |
16 | // methods annotated with such annotations per class, either one can be used |
17 | // to invalidate the ivar. An ivar or property are considered to be |
18 | // invalidated if they are being assigned 'nil' or an invalidation method has |
19 | // been called on them. An invalidation method should either invalidate all |
20 | // the ivars or call another invalidation method (on self). |
21 | // |
22 | // Partial invalidor annotation allows to address cases when ivars are |
23 | // invalidated by other methods, which might or might not be called from |
24 | // the invalidation method. The checker checks that each invalidation |
25 | // method and all the partial methods cumulatively invalidate all ivars. |
26 | // __attribute__((annotate("objc_instance_variable_invalidator_partial"))); |
27 | // |
28 | //===----------------------------------------------------------------------===// |
29 | |
30 | #include "clang/AST/Attr.h" |
31 | #include "clang/AST/DeclObjC.h" |
32 | #include "clang/AST/StmtVisitor.h" |
33 | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
34 | #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" |
35 | #include "clang/StaticAnalyzer/Core/Checker.h" |
36 | #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" |
37 | #include "llvm/ADT/DenseMap.h" |
38 | #include "llvm/ADT/STLExtras.h" |
39 | #include "llvm/ADT/SetVector.h" |
40 | |
41 | using namespace clang; |
42 | using namespace ento; |
43 | |
44 | namespace { |
45 | struct ChecksFilter { |
46 | /// Check for missing invalidation method declarations. |
47 | bool check_MissingInvalidationMethod = false; |
48 | /// Check that all ivars are invalidated. |
49 | bool check_InstanceVariableInvalidation = false; |
50 | |
51 | CheckerNameRef checkName_MissingInvalidationMethod; |
52 | CheckerNameRef checkName_InstanceVariableInvalidation; |
53 | }; |
54 | |
55 | class IvarInvalidationCheckerImpl { |
56 | typedef llvm::SmallSetVector<const ObjCMethodDecl*, 2> MethodSet; |
57 | typedef llvm::DenseMap<const ObjCMethodDecl*, |
58 | const ObjCIvarDecl*> MethToIvarMapTy; |
59 | typedef llvm::DenseMap<const ObjCPropertyDecl*, |
60 | const ObjCIvarDecl*> PropToIvarMapTy; |
61 | typedef llvm::DenseMap<const ObjCIvarDecl*, |
62 | const ObjCPropertyDecl*> IvarToPropMapTy; |
63 | |
64 | struct InvalidationInfo { |
65 | /// Has the ivar been invalidated? |
66 | bool IsInvalidated = false; |
67 | |
68 | /// The methods which can be used to invalidate the ivar. |
69 | MethodSet InvalidationMethods; |
70 | |
71 | InvalidationInfo() = default; |
72 | void addInvalidationMethod(const ObjCMethodDecl *MD) { |
73 | InvalidationMethods.insert(X: MD); |
74 | } |
75 | |
76 | bool needsInvalidation() const { |
77 | return !InvalidationMethods.empty(); |
78 | } |
79 | |
80 | bool hasMethod(const ObjCMethodDecl *MD) { |
81 | if (IsInvalidated) |
82 | return true; |
83 | for (const ObjCMethodDecl *Curr : InvalidationMethods) { |
84 | if (Curr == MD) { |
85 | IsInvalidated = true; |
86 | return true; |
87 | } |
88 | } |
89 | return false; |
90 | } |
91 | }; |
92 | |
93 | typedef llvm::DenseMap<const ObjCIvarDecl*, InvalidationInfo> IvarSet; |
94 | |
95 | /// Statement visitor, which walks the method body and flags the ivars |
96 | /// referenced in it (either directly or via property). |
97 | class MethodCrawler : public ConstStmtVisitor<MethodCrawler> { |
98 | /// The set of Ivars which need to be invalidated. |
99 | IvarSet &IVars; |
100 | |
101 | /// Flag is set as the result of a message send to another |
102 | /// invalidation method. |
103 | bool &CalledAnotherInvalidationMethod; |
104 | |
105 | /// Property setter to ivar mapping. |
106 | const MethToIvarMapTy &PropertySetterToIvarMap; |
107 | |
108 | /// Property getter to ivar mapping. |
109 | const MethToIvarMapTy &PropertyGetterToIvarMap; |
110 | |
111 | /// Property to ivar mapping. |
112 | const PropToIvarMapTy &PropertyToIvarMap; |
113 | |
114 | /// The invalidation method being currently processed. |
115 | const ObjCMethodDecl *InvalidationMethod; |
116 | |
117 | ASTContext &Ctx; |
118 | |
119 | /// Peel off parens, casts, OpaqueValueExpr, and PseudoObjectExpr. |
120 | const Expr *peel(const Expr *E) const; |
121 | |
122 | /// Does this expression represent zero: '0'? |
123 | bool isZero(const Expr *E) const; |
124 | |
125 | /// Mark the given ivar as invalidated. |
126 | void markInvalidated(const ObjCIvarDecl *Iv); |
127 | |
128 | /// Checks if IvarRef refers to the tracked IVar, if yes, marks it as |
129 | /// invalidated. |
130 | void checkObjCIvarRefExpr(const ObjCIvarRefExpr *IvarRef); |
131 | |
132 | /// Checks if ObjCPropertyRefExpr refers to the tracked IVar, if yes, marks |
133 | /// it as invalidated. |
134 | void checkObjCPropertyRefExpr(const ObjCPropertyRefExpr *PA); |
135 | |
136 | /// Checks if ObjCMessageExpr refers to (is a getter for) the tracked IVar, |
137 | /// if yes, marks it as invalidated. |
138 | void checkObjCMessageExpr(const ObjCMessageExpr *ME); |
139 | |
140 | /// Checks if the Expr refers to an ivar, if yes, marks it as invalidated. |
141 | void check(const Expr *E); |
142 | |
143 | public: |
144 | MethodCrawler(IvarSet &InIVars, |
145 | bool &InCalledAnotherInvalidationMethod, |
146 | const MethToIvarMapTy &InPropertySetterToIvarMap, |
147 | const MethToIvarMapTy &InPropertyGetterToIvarMap, |
148 | const PropToIvarMapTy &InPropertyToIvarMap, |
149 | ASTContext &InCtx) |
150 | : IVars(InIVars), |
151 | CalledAnotherInvalidationMethod(InCalledAnotherInvalidationMethod), |
152 | PropertySetterToIvarMap(InPropertySetterToIvarMap), |
153 | PropertyGetterToIvarMap(InPropertyGetterToIvarMap), |
154 | PropertyToIvarMap(InPropertyToIvarMap), |
155 | InvalidationMethod(nullptr), |
156 | Ctx(InCtx) {} |
157 | |
158 | void VisitStmt(const Stmt *S) { VisitChildren(S); } |
159 | |
160 | void VisitBinaryOperator(const BinaryOperator *BO); |
161 | |
162 | void VisitObjCMessageExpr(const ObjCMessageExpr *ME); |
163 | |
164 | void VisitChildren(const Stmt *S) { |
165 | for (const auto *Child : S->children()) { |
166 | if (Child) |
167 | this->Visit(S: Child); |
168 | if (CalledAnotherInvalidationMethod) |
169 | return; |
170 | } |
171 | } |
172 | }; |
173 | |
174 | /// Check if the any of the methods inside the interface are annotated with |
175 | /// the invalidation annotation, update the IvarInfo accordingly. |
176 | /// \param LookForPartial is set when we are searching for partial |
177 | /// invalidators. |
178 | static void containsInvalidationMethod(const ObjCContainerDecl *D, |
179 | InvalidationInfo &Out, |
180 | bool LookForPartial); |
181 | |
182 | /// Check if ivar should be tracked and add to TrackedIvars if positive. |
183 | /// Returns true if ivar should be tracked. |
184 | static bool trackIvar(const ObjCIvarDecl *Iv, IvarSet &TrackedIvars, |
185 | const ObjCIvarDecl **FirstIvarDecl); |
186 | |
187 | /// Given the property declaration, and the list of tracked ivars, finds |
188 | /// the ivar backing the property when possible. Returns '0' when no such |
189 | /// ivar could be found. |
190 | static const ObjCIvarDecl *findPropertyBackingIvar( |
191 | const ObjCPropertyDecl *Prop, |
192 | const ObjCInterfaceDecl *InterfaceD, |
193 | IvarSet &TrackedIvars, |
194 | const ObjCIvarDecl **FirstIvarDecl); |
195 | |
196 | /// Print ivar name or the property if the given ivar backs a property. |
197 | static void printIvar(llvm::raw_svector_ostream &os, |
198 | const ObjCIvarDecl *IvarDecl, |
199 | const IvarToPropMapTy &IvarToPopertyMap); |
200 | |
201 | void reportNoInvalidationMethod(CheckerNameRef CheckName, |
202 | const ObjCIvarDecl *FirstIvarDecl, |
203 | const IvarToPropMapTy &IvarToPopertyMap, |
204 | const ObjCInterfaceDecl *InterfaceD, |
205 | bool MissingDeclaration) const; |
206 | |
207 | void reportIvarNeedsInvalidation(const ObjCIvarDecl *IvarD, |
208 | const IvarToPropMapTy &IvarToPopertyMap, |
209 | const ObjCMethodDecl *MethodD) const; |
210 | |
211 | AnalysisManager& Mgr; |
212 | BugReporter &BR; |
213 | /// Filter on the checks performed. |
214 | const ChecksFilter &Filter; |
215 | |
216 | public: |
217 | IvarInvalidationCheckerImpl(AnalysisManager& InMgr, |
218 | BugReporter &InBR, |
219 | const ChecksFilter &InFilter) : |
220 | Mgr (InMgr), BR(InBR), Filter(InFilter) {} |
221 | |
222 | void visit(const ObjCImplementationDecl *D) const; |
223 | }; |
224 | |
225 | static bool isInvalidationMethod(const ObjCMethodDecl *M, bool LookForPartial) { |
226 | for (const auto *Ann : M->specific_attrs<AnnotateAttr>()) { |
227 | if (!LookForPartial && |
228 | Ann->getAnnotation() == "objc_instance_variable_invalidator" ) |
229 | return true; |
230 | if (LookForPartial && |
231 | Ann->getAnnotation() == "objc_instance_variable_invalidator_partial" ) |
232 | return true; |
233 | } |
234 | return false; |
235 | } |
236 | |
237 | void IvarInvalidationCheckerImpl::containsInvalidationMethod( |
238 | const ObjCContainerDecl *D, InvalidationInfo &OutInfo, bool Partial) { |
239 | |
240 | if (!D) |
241 | return; |
242 | |
243 | assert(!isa<ObjCImplementationDecl>(D)); |
244 | // TODO: Cache the results. |
245 | |
246 | // Check all methods. |
247 | for (const auto *MDI : D->methods()) |
248 | if (isInvalidationMethod(M: MDI, LookForPartial: Partial)) |
249 | OutInfo.addInvalidationMethod( |
250 | MD: cast<ObjCMethodDecl>(Val: MDI->getCanonicalDecl())); |
251 | |
252 | // If interface, check all parent protocols and super. |
253 | if (const ObjCInterfaceDecl *InterfD = dyn_cast<ObjCInterfaceDecl>(Val: D)) { |
254 | |
255 | // Visit all protocols. |
256 | for (const auto *I : InterfD->protocols()) |
257 | containsInvalidationMethod(D: I->getDefinition(), OutInfo, Partial); |
258 | |
259 | // Visit all categories in case the invalidation method is declared in |
260 | // a category. |
261 | for (const auto *Ext : InterfD->visible_extensions()) |
262 | containsInvalidationMethod(D: Ext, OutInfo, Partial); |
263 | |
264 | containsInvalidationMethod(D: InterfD->getSuperClass(), OutInfo, Partial); |
265 | return; |
266 | } |
267 | |
268 | // If protocol, check all parent protocols. |
269 | if (const ObjCProtocolDecl *ProtD = dyn_cast<ObjCProtocolDecl>(Val: D)) { |
270 | for (const auto *I : ProtD->protocols()) { |
271 | containsInvalidationMethod(D: I->getDefinition(), OutInfo, Partial); |
272 | } |
273 | return; |
274 | } |
275 | } |
276 | |
277 | bool IvarInvalidationCheckerImpl::trackIvar(const ObjCIvarDecl *Iv, |
278 | IvarSet &TrackedIvars, |
279 | const ObjCIvarDecl **FirstIvarDecl) { |
280 | QualType IvQTy = Iv->getType(); |
281 | const ObjCObjectPointerType *IvTy = IvQTy->getAs<ObjCObjectPointerType>(); |
282 | if (!IvTy) |
283 | return false; |
284 | const ObjCInterfaceDecl *IvInterf = IvTy->getInterfaceDecl(); |
285 | |
286 | InvalidationInfo Info; |
287 | containsInvalidationMethod(D: IvInterf, OutInfo&: Info, /*LookForPartial*/ Partial: false); |
288 | if (Info.needsInvalidation()) { |
289 | const ObjCIvarDecl *I = cast<ObjCIvarDecl>(Val: Iv->getCanonicalDecl()); |
290 | TrackedIvars[I] = Info; |
291 | if (!*FirstIvarDecl) |
292 | *FirstIvarDecl = I; |
293 | return true; |
294 | } |
295 | return false; |
296 | } |
297 | |
298 | const ObjCIvarDecl *IvarInvalidationCheckerImpl::findPropertyBackingIvar( |
299 | const ObjCPropertyDecl *Prop, |
300 | const ObjCInterfaceDecl *InterfaceD, |
301 | IvarSet &TrackedIvars, |
302 | const ObjCIvarDecl **FirstIvarDecl) { |
303 | const ObjCIvarDecl *IvarD = nullptr; |
304 | |
305 | // Lookup for the synthesized case. |
306 | IvarD = Prop->getPropertyIvarDecl(); |
307 | // We only track the ivars/properties that are defined in the current |
308 | // class (not the parent). |
309 | if (IvarD && IvarD->getContainingInterface() == InterfaceD) { |
310 | if (TrackedIvars.count(Val: IvarD)) { |
311 | return IvarD; |
312 | } |
313 | // If the ivar is synthesized we still want to track it. |
314 | if (trackIvar(Iv: IvarD, TrackedIvars, FirstIvarDecl)) |
315 | return IvarD; |
316 | } |
317 | |
318 | // Lookup IVars named "_PropName"or "PropName" among the tracked Ivars. |
319 | StringRef PropName = Prop->getIdentifier()->getName(); |
320 | for (const ObjCIvarDecl *Iv : llvm::make_first_range(c&: TrackedIvars)) { |
321 | StringRef IvarName = Iv->getName(); |
322 | |
323 | if (IvarName == PropName) |
324 | return Iv; |
325 | |
326 | SmallString<128> PropNameWithUnderscore; |
327 | { |
328 | llvm::raw_svector_ostream os(PropNameWithUnderscore); |
329 | os << '_' << PropName; |
330 | } |
331 | if (IvarName == PropNameWithUnderscore) |
332 | return Iv; |
333 | } |
334 | |
335 | // Note, this is a possible source of false positives. We could look at the |
336 | // getter implementation to find the ivar when its name is not derived from |
337 | // the property name. |
338 | return nullptr; |
339 | } |
340 | |
341 | void IvarInvalidationCheckerImpl::printIvar(llvm::raw_svector_ostream &os, |
342 | const ObjCIvarDecl *IvarDecl, |
343 | const IvarToPropMapTy &IvarToPopertyMap) { |
344 | if (IvarDecl->getSynthesize()) { |
345 | const ObjCPropertyDecl *PD = IvarToPopertyMap.lookup(Val: IvarDecl); |
346 | assert(PD &&"Do we synthesize ivars for something other than properties?" ); |
347 | os << "Property " << PD->getName() << " " ; |
348 | } else { |
349 | os << "Instance variable " << IvarDecl->getName() << " " ; |
350 | } |
351 | } |
352 | |
353 | // Check that the invalidatable interfaces with ivars/properties implement the |
354 | // invalidation methods. |
355 | void IvarInvalidationCheckerImpl:: |
356 | visit(const ObjCImplementationDecl *ImplD) const { |
357 | // Collect all ivars that need cleanup. |
358 | IvarSet Ivars; |
359 | // Record the first Ivar needing invalidation; used in reporting when only |
360 | // one ivar is sufficient. Cannot grab the first on the Ivars set to ensure |
361 | // deterministic output. |
362 | const ObjCIvarDecl *FirstIvarDecl = nullptr; |
363 | const ObjCInterfaceDecl *InterfaceD = ImplD->getClassInterface(); |
364 | |
365 | // Collect ivars declared in this class, its extensions and its implementation |
366 | ObjCInterfaceDecl *IDecl = const_cast<ObjCInterfaceDecl *>(InterfaceD); |
367 | for (const ObjCIvarDecl *Iv = IDecl->all_declared_ivar_begin(); Iv; |
368 | Iv= Iv->getNextIvar()) |
369 | trackIvar(Iv, TrackedIvars&: Ivars, FirstIvarDecl: &FirstIvarDecl); |
370 | |
371 | // Construct Property/Property Accessor to Ivar maps to assist checking if an |
372 | // ivar which is backing a property has been reset. |
373 | MethToIvarMapTy PropSetterToIvarMap; |
374 | MethToIvarMapTy PropGetterToIvarMap; |
375 | PropToIvarMapTy PropertyToIvarMap; |
376 | IvarToPropMapTy IvarToPopertyMap; |
377 | |
378 | ObjCInterfaceDecl::PropertyMap PropMap; |
379 | InterfaceD->collectPropertiesToImplement(PM&: PropMap); |
380 | |
381 | for (const ObjCPropertyDecl *PD : llvm::make_second_range(c&: PropMap)) { |
382 | if (PD->isClassProperty()) |
383 | continue; |
384 | |
385 | const ObjCIvarDecl *ID = findPropertyBackingIvar(Prop: PD, InterfaceD, TrackedIvars&: Ivars, |
386 | FirstIvarDecl: &FirstIvarDecl); |
387 | if (!ID) |
388 | continue; |
389 | |
390 | // Store the mappings. |
391 | PD = cast<ObjCPropertyDecl>(Val: PD->getCanonicalDecl()); |
392 | PropertyToIvarMap[PD] = ID; |
393 | IvarToPopertyMap[ID] = PD; |
394 | |
395 | // Find the setter and the getter. |
396 | const ObjCMethodDecl *SetterD = PD->getSetterMethodDecl(); |
397 | if (SetterD) { |
398 | SetterD = SetterD->getCanonicalDecl(); |
399 | PropSetterToIvarMap[SetterD] = ID; |
400 | } |
401 | |
402 | const ObjCMethodDecl *GetterD = PD->getGetterMethodDecl(); |
403 | if (GetterD) { |
404 | GetterD = GetterD->getCanonicalDecl(); |
405 | PropGetterToIvarMap[GetterD] = ID; |
406 | } |
407 | } |
408 | |
409 | // If no ivars need invalidation, there is nothing to check here. |
410 | if (Ivars.empty()) |
411 | return; |
412 | |
413 | // Find all partial invalidation methods. |
414 | InvalidationInfo PartialInfo; |
415 | containsInvalidationMethod(D: InterfaceD, OutInfo&: PartialInfo, /*LookForPartial*/ Partial: true); |
416 | |
417 | // Remove ivars invalidated by the partial invalidation methods. They do not |
418 | // need to be invalidated in the regular invalidation methods. |
419 | bool AtImplementationContainsAtLeastOnePartialInvalidationMethod = false; |
420 | for (const ObjCMethodDecl *InterfD : PartialInfo.InvalidationMethods) { |
421 | // Get the corresponding method in the @implementation. |
422 | const ObjCMethodDecl *D = ImplD->getMethod(Sel: InterfD->getSelector(), |
423 | isInstance: InterfD->isInstanceMethod()); |
424 | if (D && D->hasBody()) { |
425 | AtImplementationContainsAtLeastOnePartialInvalidationMethod = true; |
426 | |
427 | bool CalledAnotherInvalidationMethod = false; |
428 | // The MethodCrowler is going to remove the invalidated ivars. |
429 | MethodCrawler(Ivars, |
430 | CalledAnotherInvalidationMethod, |
431 | PropSetterToIvarMap, |
432 | PropGetterToIvarMap, |
433 | PropertyToIvarMap, |
434 | BR.getContext()).VisitStmt(S: D->getBody()); |
435 | // If another invalidation method was called, trust that full invalidation |
436 | // has occurred. |
437 | if (CalledAnotherInvalidationMethod) |
438 | Ivars.clear(); |
439 | } |
440 | } |
441 | |
442 | // If all ivars have been invalidated by partial invalidators, there is |
443 | // nothing to check here. |
444 | if (Ivars.empty()) |
445 | return; |
446 | |
447 | // Find all invalidation methods in this @interface declaration and parents. |
448 | InvalidationInfo Info; |
449 | containsInvalidationMethod(D: InterfaceD, OutInfo&: Info, /*LookForPartial*/ Partial: false); |
450 | |
451 | // Report an error in case none of the invalidation methods are declared. |
452 | if (!Info.needsInvalidation() && !PartialInfo.needsInvalidation()) { |
453 | if (Filter.check_MissingInvalidationMethod) |
454 | reportNoInvalidationMethod(CheckName: Filter.checkName_MissingInvalidationMethod, |
455 | FirstIvarDecl, IvarToPopertyMap, InterfaceD, |
456 | /*MissingDeclaration*/ true); |
457 | // If there are no invalidation methods, there is no ivar validation work |
458 | // to be done. |
459 | return; |
460 | } |
461 | |
462 | // Only check if Ivars are invalidated when InstanceVariableInvalidation |
463 | // has been requested. |
464 | if (!Filter.check_InstanceVariableInvalidation) |
465 | return; |
466 | |
467 | // Check that all ivars are invalidated by the invalidation methods. |
468 | bool AtImplementationContainsAtLeastOneInvalidationMethod = false; |
469 | for (const ObjCMethodDecl *InterfD : Info.InvalidationMethods) { |
470 | // Get the corresponding method in the @implementation. |
471 | const ObjCMethodDecl *D = ImplD->getMethod(Sel: InterfD->getSelector(), |
472 | isInstance: InterfD->isInstanceMethod()); |
473 | if (D && D->hasBody()) { |
474 | AtImplementationContainsAtLeastOneInvalidationMethod = true; |
475 | |
476 | // Get a copy of ivars needing invalidation. |
477 | IvarSet IvarsI = Ivars; |
478 | |
479 | bool CalledAnotherInvalidationMethod = false; |
480 | MethodCrawler(IvarsI, |
481 | CalledAnotherInvalidationMethod, |
482 | PropSetterToIvarMap, |
483 | PropGetterToIvarMap, |
484 | PropertyToIvarMap, |
485 | BR.getContext()).VisitStmt(S: D->getBody()); |
486 | // If another invalidation method was called, trust that full invalidation |
487 | // has occurred. |
488 | if (CalledAnotherInvalidationMethod) |
489 | continue; |
490 | |
491 | // Warn on the ivars that were not invalidated by the method. |
492 | for (const ObjCIvarDecl *Ivar : llvm::make_first_range(c&: IvarsI)) |
493 | reportIvarNeedsInvalidation(IvarD: Ivar, IvarToPopertyMap, MethodD: D); |
494 | } |
495 | } |
496 | |
497 | // Report an error in case none of the invalidation methods are implemented. |
498 | if (!AtImplementationContainsAtLeastOneInvalidationMethod) { |
499 | if (AtImplementationContainsAtLeastOnePartialInvalidationMethod) { |
500 | // Warn on the ivars that were not invalidated by the prrtial |
501 | // invalidation methods. |
502 | for (const ObjCIvarDecl *Ivar : llvm::make_first_range(c&: Ivars)) |
503 | reportIvarNeedsInvalidation(IvarD: Ivar, IvarToPopertyMap, MethodD: nullptr); |
504 | } else { |
505 | // Otherwise, no invalidation methods were implemented. |
506 | reportNoInvalidationMethod(CheckName: Filter.checkName_InstanceVariableInvalidation, |
507 | FirstIvarDecl, IvarToPopertyMap, InterfaceD, |
508 | /*MissingDeclaration*/ false); |
509 | } |
510 | } |
511 | } |
512 | |
513 | void IvarInvalidationCheckerImpl::reportNoInvalidationMethod( |
514 | CheckerNameRef CheckName, const ObjCIvarDecl *FirstIvarDecl, |
515 | const IvarToPropMapTy &IvarToPopertyMap, |
516 | const ObjCInterfaceDecl *InterfaceD, bool MissingDeclaration) const { |
517 | SmallString<128> sbuf; |
518 | llvm::raw_svector_ostream os(sbuf); |
519 | assert(FirstIvarDecl); |
520 | printIvar(os, IvarDecl: FirstIvarDecl, IvarToPopertyMap); |
521 | os << "needs to be invalidated; " ; |
522 | if (MissingDeclaration) |
523 | os << "no invalidation method is declared for " ; |
524 | else |
525 | os << "no invalidation method is defined in the @implementation for " ; |
526 | os << InterfaceD->getName(); |
527 | |
528 | PathDiagnosticLocation IvarDecLocation = |
529 | PathDiagnosticLocation::createBegin(D: FirstIvarDecl, SM: BR.getSourceManager()); |
530 | |
531 | BR.EmitBasicReport(DeclWithIssue: FirstIvarDecl, CheckerName: CheckName, BugName: "Incomplete invalidation" , |
532 | BugCategory: categories::CoreFoundationObjectiveC, BugStr: os.str(), |
533 | Loc: IvarDecLocation); |
534 | } |
535 | |
536 | void IvarInvalidationCheckerImpl:: |
537 | reportIvarNeedsInvalidation(const ObjCIvarDecl *IvarD, |
538 | const IvarToPropMapTy &IvarToPopertyMap, |
539 | const ObjCMethodDecl *MethodD) const { |
540 | SmallString<128> sbuf; |
541 | llvm::raw_svector_ostream os(sbuf); |
542 | printIvar(os, IvarDecl: IvarD, IvarToPopertyMap); |
543 | os << "needs to be invalidated or set to nil" ; |
544 | if (MethodD) { |
545 | PathDiagnosticLocation MethodDecLocation = |
546 | PathDiagnosticLocation::createEnd(S: MethodD->getBody(), |
547 | SM: BR.getSourceManager(), |
548 | LAC: Mgr.getAnalysisDeclContext(D: MethodD)); |
549 | BR.EmitBasicReport(DeclWithIssue: MethodD, CheckerName: Filter.checkName_InstanceVariableInvalidation, |
550 | BugName: "Incomplete invalidation" , |
551 | BugCategory: categories::CoreFoundationObjectiveC, BugStr: os.str(), |
552 | Loc: MethodDecLocation); |
553 | } else { |
554 | BR.EmitBasicReport( |
555 | DeclWithIssue: IvarD, CheckerName: Filter.checkName_InstanceVariableInvalidation, |
556 | BugName: "Incomplete invalidation" , BugCategory: categories::CoreFoundationObjectiveC, |
557 | BugStr: os.str(), |
558 | Loc: PathDiagnosticLocation::createBegin(D: IvarD, SM: BR.getSourceManager())); |
559 | } |
560 | } |
561 | |
562 | void IvarInvalidationCheckerImpl::MethodCrawler::markInvalidated( |
563 | const ObjCIvarDecl *Iv) { |
564 | IvarSet::iterator I = IVars.find(Val: Iv); |
565 | if (I != IVars.end()) { |
566 | // If InvalidationMethod is present, we are processing the message send and |
567 | // should ensure we are invalidating with the appropriate method, |
568 | // otherwise, we are processing setting to 'nil'. |
569 | if (!InvalidationMethod || I->second.hasMethod(MD: InvalidationMethod)) |
570 | IVars.erase(I); |
571 | } |
572 | } |
573 | |
574 | const Expr *IvarInvalidationCheckerImpl::MethodCrawler::peel(const Expr *E) const { |
575 | E = E->IgnoreParenCasts(); |
576 | if (const PseudoObjectExpr *POE = dyn_cast<PseudoObjectExpr>(Val: E)) |
577 | E = POE->getSyntacticForm()->IgnoreParenCasts(); |
578 | if (const OpaqueValueExpr *OVE = dyn_cast<OpaqueValueExpr>(Val: E)) |
579 | E = OVE->getSourceExpr()->IgnoreParenCasts(); |
580 | return E; |
581 | } |
582 | |
583 | void IvarInvalidationCheckerImpl::MethodCrawler::checkObjCIvarRefExpr( |
584 | const ObjCIvarRefExpr *IvarRef) { |
585 | if (const Decl *D = IvarRef->getDecl()) |
586 | markInvalidated(Iv: cast<ObjCIvarDecl>(Val: D->getCanonicalDecl())); |
587 | } |
588 | |
589 | void IvarInvalidationCheckerImpl::MethodCrawler::checkObjCMessageExpr( |
590 | const ObjCMessageExpr *ME) { |
591 | const ObjCMethodDecl *MD = ME->getMethodDecl(); |
592 | if (MD) { |
593 | MD = MD->getCanonicalDecl(); |
594 | MethToIvarMapTy::const_iterator IvI = PropertyGetterToIvarMap.find(Val: MD); |
595 | if (IvI != PropertyGetterToIvarMap.end()) |
596 | markInvalidated(Iv: IvI->second); |
597 | } |
598 | } |
599 | |
600 | void IvarInvalidationCheckerImpl::MethodCrawler::checkObjCPropertyRefExpr( |
601 | const ObjCPropertyRefExpr *PA) { |
602 | |
603 | if (PA->isExplicitProperty()) { |
604 | const ObjCPropertyDecl *PD = PA->getExplicitProperty(); |
605 | if (PD) { |
606 | PD = cast<ObjCPropertyDecl>(Val: PD->getCanonicalDecl()); |
607 | PropToIvarMapTy::const_iterator IvI = PropertyToIvarMap.find(Val: PD); |
608 | if (IvI != PropertyToIvarMap.end()) |
609 | markInvalidated(Iv: IvI->second); |
610 | return; |
611 | } |
612 | } |
613 | |
614 | if (PA->isImplicitProperty()) { |
615 | const ObjCMethodDecl *MD = PA->getImplicitPropertySetter(); |
616 | if (MD) { |
617 | MD = MD->getCanonicalDecl(); |
618 | MethToIvarMapTy::const_iterator IvI =PropertyGetterToIvarMap.find(Val: MD); |
619 | if (IvI != PropertyGetterToIvarMap.end()) |
620 | markInvalidated(Iv: IvI->second); |
621 | return; |
622 | } |
623 | } |
624 | } |
625 | |
626 | bool IvarInvalidationCheckerImpl::MethodCrawler::isZero(const Expr *E) const { |
627 | E = peel(E); |
628 | |
629 | return (E->isNullPointerConstant(Ctx, NPC: Expr::NPC_ValueDependentIsNotNull) |
630 | != Expr::NPCK_NotNull); |
631 | } |
632 | |
633 | void IvarInvalidationCheckerImpl::MethodCrawler::check(const Expr *E) { |
634 | E = peel(E); |
635 | |
636 | if (const ObjCIvarRefExpr *IvarRef = dyn_cast<ObjCIvarRefExpr>(Val: E)) { |
637 | checkObjCIvarRefExpr(IvarRef); |
638 | return; |
639 | } |
640 | |
641 | if (const ObjCPropertyRefExpr *PropRef = dyn_cast<ObjCPropertyRefExpr>(Val: E)) { |
642 | checkObjCPropertyRefExpr(PA: PropRef); |
643 | return; |
644 | } |
645 | |
646 | if (const ObjCMessageExpr *MsgExpr = dyn_cast<ObjCMessageExpr>(Val: E)) { |
647 | checkObjCMessageExpr(ME: MsgExpr); |
648 | return; |
649 | } |
650 | } |
651 | |
652 | void IvarInvalidationCheckerImpl::MethodCrawler::VisitBinaryOperator( |
653 | const BinaryOperator *BO) { |
654 | VisitStmt(S: BO); |
655 | |
656 | // Do we assign/compare against zero? If yes, check the variable we are |
657 | // assigning to. |
658 | BinaryOperatorKind Opcode = BO->getOpcode(); |
659 | if (Opcode != BO_Assign && |
660 | Opcode != BO_EQ && |
661 | Opcode != BO_NE) |
662 | return; |
663 | |
664 | if (isZero(E: BO->getRHS())) { |
665 | check(E: BO->getLHS()); |
666 | return; |
667 | } |
668 | |
669 | if (Opcode != BO_Assign && isZero(E: BO->getLHS())) { |
670 | check(E: BO->getRHS()); |
671 | return; |
672 | } |
673 | } |
674 | |
675 | void IvarInvalidationCheckerImpl::MethodCrawler::VisitObjCMessageExpr( |
676 | const ObjCMessageExpr *ME) { |
677 | const ObjCMethodDecl *MD = ME->getMethodDecl(); |
678 | const Expr *Receiver = ME->getInstanceReceiver(); |
679 | |
680 | // Stop if we are calling '[self invalidate]'. |
681 | if (Receiver && isInvalidationMethod(M: MD, /*LookForPartial*/ false)) |
682 | if (Receiver->isObjCSelfExpr()) { |
683 | CalledAnotherInvalidationMethod = true; |
684 | return; |
685 | } |
686 | |
687 | // Check if we call a setter and set the property to 'nil'. |
688 | if (MD && (ME->getNumArgs() == 1) && isZero(E: ME->getArg(Arg: 0))) { |
689 | MD = MD->getCanonicalDecl(); |
690 | MethToIvarMapTy::const_iterator IvI = PropertySetterToIvarMap.find(Val: MD); |
691 | if (IvI != PropertySetterToIvarMap.end()) { |
692 | markInvalidated(Iv: IvI->second); |
693 | return; |
694 | } |
695 | } |
696 | |
697 | // Check if we call the 'invalidation' routine on the ivar. |
698 | if (Receiver) { |
699 | InvalidationMethod = MD; |
700 | check(E: Receiver->IgnoreParenCasts()); |
701 | InvalidationMethod = nullptr; |
702 | } |
703 | |
704 | VisitStmt(S: ME); |
705 | } |
706 | } // end anonymous namespace |
707 | |
708 | // Register the checkers. |
709 | namespace { |
710 | class IvarInvalidationChecker : |
711 | public Checker<check::ASTDecl<ObjCImplementationDecl> > { |
712 | public: |
713 | ChecksFilter Filter; |
714 | public: |
715 | void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager& Mgr, |
716 | BugReporter &BR) const { |
717 | IvarInvalidationCheckerImpl Walker(Mgr, BR, Filter); |
718 | Walker.visit(ImplD: D); |
719 | } |
720 | }; |
721 | } // end anonymous namespace |
722 | |
723 | void ento::registerIvarInvalidationModeling(CheckerManager &mgr) { |
724 | mgr.registerChecker<IvarInvalidationChecker>(); |
725 | } |
726 | |
727 | bool ento::shouldRegisterIvarInvalidationModeling(const CheckerManager &mgr) { |
728 | return true; |
729 | } |
730 | |
731 | #define REGISTER_CHECKER(name) \ |
732 | void ento::register##name(CheckerManager &mgr) { \ |
733 | IvarInvalidationChecker *checker = \ |
734 | mgr.getChecker<IvarInvalidationChecker>(); \ |
735 | checker->Filter.check_##name = true; \ |
736 | checker->Filter.checkName_##name = mgr.getCurrentCheckerName(); \ |
737 | } \ |
738 | \ |
739 | bool ento::shouldRegister##name(const CheckerManager &mgr) { return true; } |
740 | |
741 | REGISTER_CHECKER(InstanceVariableInvalidation) |
742 | REGISTER_CHECKER(MissingInvalidationMethod) |
743 | |