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