/Users/buildslave/jenkins/workspace/clang-stage2-coverage-R/llvm/tools/clang/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp
Line | Count | Source (jump to first uncovered line) |
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/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
31 | | #include "clang/AST/Attr.h" |
32 | | #include "clang/AST/DeclObjC.h" |
33 | | #include "clang/AST/StmtVisitor.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/SetVector.h" |
39 | | #include "llvm/ADT/SmallString.h" |
40 | | |
41 | | using namespace clang; |
42 | | using namespace ento; |
43 | | |
44 | | namespace { |
45 | | struct ChecksFilter { |
46 | | /// Check for missing invalidation method declarations. |
47 | | DefaultBool check_MissingInvalidationMethod; |
48 | | /// Check that all ivars are invalidated. |
49 | | DefaultBool check_InstanceVariableInvalidation; |
50 | | |
51 | | CheckName checkName_MissingInvalidationMethod; |
52 | | CheckName 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; |
67 | | |
68 | | /// The methods which can be used to invalidate the ivar. |
69 | | MethodSet InvalidationMethods; |
70 | | |
71 | 216 | InvalidationInfo() : IsInvalidated(false) {} |
72 | 236 | void addInvalidationMethod(const ObjCMethodDecl *MD) { |
73 | 236 | InvalidationMethods.insert(MD); |
74 | 236 | } |
75 | | |
76 | 120 | bool needsInvalidation() const { |
77 | 120 | return !InvalidationMethods.empty(); |
78 | 120 | } |
79 | | |
80 | 26 | bool hasMethod(const ObjCMethodDecl *MD) { |
81 | 26 | if (IsInvalidated) |
82 | 0 | return true; |
83 | 26 | for (MethodSet::iterator I = InvalidationMethods.begin(), |
84 | 38 | E = InvalidationMethods.end(); I != E; ++I12 ) { |
85 | 35 | if (*I == MD) { |
86 | 23 | IsInvalidated = true; |
87 | 23 | return true; |
88 | 23 | } |
89 | 35 | } |
90 | 26 | return false3 ; |
91 | 26 | } |
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 | 25 | Ctx(InCtx) {} |
158 | | |
159 | 415 | void VisitStmt(const Stmt *S) { VisitChildren(S); } |
160 | | |
161 | | void VisitBinaryOperator(const BinaryOperator *BO); |
162 | | |
163 | | void VisitObjCMessageExpr(const ObjCMessageExpr *ME); |
164 | | |
165 | 415 | void VisitChildren(const Stmt *S) { |
166 | 415 | for (const auto *Child : S->children()) { |
167 | 408 | if (Child) |
168 | 408 | this->Visit(Child); |
169 | 408 | if (CalledAnotherInvalidationMethod) |
170 | 3 | return; |
171 | 408 | } |
172 | 415 | } |
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(CheckName 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 | 28 | Mgr (InMgr), BR(InBR), Filter(InFilter) {} |
222 | | |
223 | | void visit(const ObjCImplementationDecl *D) const; |
224 | | }; |
225 | | |
226 | 1.48k | static bool isInvalidationMethod(const ObjCMethodDecl *M, bool LookForPartial) { |
227 | 1.48k | for (const auto *Ann : M->specific_attrs<AnnotateAttr>()) { |
228 | 324 | if (!LookForPartial && |
229 | 324 | Ann->getAnnotation() == "objc_instance_variable_invalidator"262 ) |
230 | 244 | return true; |
231 | 80 | if (LookForPartial && |
232 | 80 | Ann->getAnnotation() == "objc_instance_variable_invalidator_partial"62 ) |
233 | 18 | return true; |
234 | 80 | } |
235 | 1.48k | return false1.22k ; |
236 | 1.48k | } |
237 | | |
238 | | void IvarInvalidationCheckerImpl::containsInvalidationMethod( |
239 | 938 | const ObjCContainerDecl *D, InvalidationInfo &OutInfo, bool Partial) { |
240 | 938 | |
241 | 938 | if (!D) |
242 | 138 | return; |
243 | 800 | |
244 | 800 | assert(!isa<ObjCImplementationDecl>(D)); |
245 | 800 | // TODO: Cache the results. |
246 | 800 | |
247 | 800 | // Check all methods. |
248 | 800 | for (const auto *MDI : D->methods()) |
249 | 1.42k | if (isInvalidationMethod(MDI, Partial)) |
250 | 236 | OutInfo.addInvalidationMethod( |
251 | 236 | cast<ObjCMethodDecl>(MDI->getCanonicalDecl())); |
252 | 800 | |
253 | 800 | // If interface, check all parent protocols and super. |
254 | 800 | if (const ObjCInterfaceDecl *InterfD = dyn_cast<ObjCInterfaceDecl>(D)) { |
255 | 276 | |
256 | 276 | // Visit all protocols. |
257 | 276 | for (const auto *I : InterfD->protocols()) |
258 | 322 | containsInvalidationMethod(I->getDefinition(), OutInfo, Partial); |
259 | 276 | |
260 | 276 | // Visit all categories in case the invalidation method is declared in |
261 | 276 | // a category. |
262 | 276 | for (const auto *Ext : InterfD->visible_extensions()) |
263 | 6 | containsInvalidationMethod(Ext, OutInfo, Partial); |
264 | 276 | |
265 | 276 | containsInvalidationMethod(InterfD->getSuperClass(), OutInfo, Partial); |
266 | 276 | return; |
267 | 276 | } |
268 | 524 | |
269 | 524 | // If protocol, check all parent protocols. |
270 | 524 | if (const ObjCProtocolDecl *ProtD = dyn_cast<ObjCProtocolDecl>(D)) { |
271 | 518 | for (const auto *I : ProtD->protocols()) { |
272 | 196 | containsInvalidationMethod(I->getDefinition(), OutInfo, Partial); |
273 | 196 | } |
274 | 518 | return; |
275 | 518 | } |
276 | 524 | } |
277 | | |
278 | | bool IvarInvalidationCheckerImpl::trackIvar(const ObjCIvarDecl *Iv, |
279 | | IvarSet &TrackedIvars, |
280 | 94 | const ObjCIvarDecl **FirstIvarDecl) { |
281 | 94 | QualType IvQTy = Iv->getType(); |
282 | 94 | const ObjCObjectPointerType *IvTy = IvQTy->getAs<ObjCObjectPointerType>(); |
283 | 94 | if (!IvTy) |
284 | 0 | return false; |
285 | 94 | const ObjCInterfaceDecl *IvInterf = IvTy->getInterfaceDecl(); |
286 | 94 | |
287 | 94 | InvalidationInfo Info; |
288 | 94 | containsInvalidationMethod(IvInterf, Info, /*LookForPartial*/ false); |
289 | 94 | if (Info.needsInvalidation()) { |
290 | 78 | const ObjCIvarDecl *I = cast<ObjCIvarDecl>(Iv->getCanonicalDecl()); |
291 | 78 | TrackedIvars[I] = Info; |
292 | 78 | if (!*FirstIvarDecl) |
293 | 24 | *FirstIvarDecl = I; |
294 | 78 | return true; |
295 | 78 | } |
296 | 16 | return false; |
297 | 16 | } |
298 | | |
299 | | const ObjCIvarDecl *IvarInvalidationCheckerImpl::findPropertyBackingIvar( |
300 | | const ObjCPropertyDecl *Prop, |
301 | | const ObjCInterfaceDecl *InterfaceD, |
302 | | IvarSet &TrackedIvars, |
303 | 36 | const ObjCIvarDecl **FirstIvarDecl) { |
304 | 36 | const ObjCIvarDecl *IvarD = nullptr; |
305 | 36 | |
306 | 36 | // Lookup for the synthesized case. |
307 | 36 | IvarD = Prop->getPropertyIvarDecl(); |
308 | 36 | // We only track the ivars/properties that are defined in the current |
309 | 36 | // class (not the parent). |
310 | 36 | if (IvarD && IvarD->getContainingInterface() == InterfaceD34 ) { |
311 | 32 | if (TrackedIvars.count(IvarD)) { |
312 | 26 | return IvarD; |
313 | 26 | } |
314 | 6 | // If the ivar is synthesized we still want to track it. |
315 | 6 | if (trackIvar(IvarD, TrackedIvars, FirstIvarDecl)) |
316 | 0 | return IvarD; |
317 | 10 | } |
318 | 10 | |
319 | 10 | // Lookup IVars named "_PropName"or "PropName" among the tracked Ivars. |
320 | 10 | StringRef PropName = Prop->getIdentifier()->getName(); |
321 | 10 | for (IvarSet::const_iterator I = TrackedIvars.begin(), |
322 | 159 | E = TrackedIvars.end(); I != E; ++I149 ) { |
323 | 151 | const ObjCIvarDecl *Iv = I->first; |
324 | 151 | StringRef IvarName = Iv->getName(); |
325 | 151 | |
326 | 151 | if (IvarName == PropName) |
327 | 0 | return Iv; |
328 | 151 | |
329 | 151 | SmallString<128> PropNameWithUnderscore; |
330 | 151 | { |
331 | 151 | llvm::raw_svector_ostream os(PropNameWithUnderscore); |
332 | 151 | os << '_' << PropName; |
333 | 151 | } |
334 | 151 | if (IvarName == PropNameWithUnderscore) |
335 | 2 | return Iv; |
336 | 151 | } |
337 | 10 | |
338 | 10 | // Note, this is a possible source of false positives. We could look at the |
339 | 10 | // getter implementation to find the ivar when its name is not derived from |
340 | 10 | // the property name. |
341 | 10 | return nullptr8 ; |
342 | 10 | } |
343 | | |
344 | | void IvarInvalidationCheckerImpl::printIvar(llvm::raw_svector_ostream &os, |
345 | | const ObjCIvarDecl *IvarDecl, |
346 | 33 | const IvarToPropMapTy &IvarToPopertyMap) { |
347 | 33 | if (IvarDecl->getSynthesize()) { |
348 | 4 | const ObjCPropertyDecl *PD = IvarToPopertyMap.lookup(IvarDecl); |
349 | 4 | assert(PD &&"Do we synthesize ivars for something other than properties?"); |
350 | 4 | os << "Property "<< PD->getName() << " "; |
351 | 29 | } else { |
352 | 29 | os << "Instance variable "<< IvarDecl->getName() << " "; |
353 | 29 | } |
354 | 33 | } |
355 | | |
356 | | // Check that the invalidatable interfaces with ivars/properties implement the |
357 | | // invalidation methods. |
358 | | void IvarInvalidationCheckerImpl:: |
359 | 28 | visit(const ObjCImplementationDecl *ImplD) const { |
360 | 28 | // Collect all ivars that need cleanup. |
361 | 28 | IvarSet Ivars; |
362 | 28 | // Record the first Ivar needing invalidation; used in reporting when only |
363 | 28 | // one ivar is sufficient. Cannot grab the first on the Ivars set to ensure |
364 | 28 | // deterministic output. |
365 | 28 | const ObjCIvarDecl *FirstIvarDecl = nullptr; |
366 | 28 | const ObjCInterfaceDecl *InterfaceD = ImplD->getClassInterface(); |
367 | 28 | |
368 | 28 | // Collect ivars declared in this class, its extensions and its implementation |
369 | 28 | ObjCInterfaceDecl *IDecl = const_cast<ObjCInterfaceDecl *>(InterfaceD); |
370 | 116 | for (const ObjCIvarDecl *Iv = IDecl->all_declared_ivar_begin(); Iv; |
371 | 88 | Iv= Iv->getNextIvar()) |
372 | 88 | trackIvar(Iv, Ivars, &FirstIvarDecl); |
373 | 28 | |
374 | 28 | // Construct Property/Property Accessor to Ivar maps to assist checking if an |
375 | 28 | // ivar which is backing a property has been reset. |
376 | 28 | MethToIvarMapTy PropSetterToIvarMap; |
377 | 28 | MethToIvarMapTy PropGetterToIvarMap; |
378 | 28 | PropToIvarMapTy PropertyToIvarMap; |
379 | 28 | IvarToPropMapTy IvarToPopertyMap; |
380 | 28 | |
381 | 28 | ObjCInterfaceDecl::PropertyMap PropMap; |
382 | 28 | ObjCInterfaceDecl::PropertyDeclOrder PropOrder; |
383 | 28 | InterfaceD->collectPropertiesToImplement(PropMap, PropOrder); |
384 | 28 | |
385 | 28 | for (ObjCInterfaceDecl::PropertyMap::iterator |
386 | 64 | I = PropMap.begin(), E = PropMap.end(); I != E; ++I36 ) { |
387 | 36 | const ObjCPropertyDecl *PD = I->second; |
388 | 36 | if (PD->isClassProperty()) |
389 | 0 | continue; |
390 | 36 | |
391 | 36 | const ObjCIvarDecl *ID = findPropertyBackingIvar(PD, InterfaceD, Ivars, |
392 | 36 | &FirstIvarDecl); |
393 | 36 | if (!ID) |
394 | 8 | continue; |
395 | 28 | |
396 | 28 | // Store the mappings. |
397 | 28 | PD = cast<ObjCPropertyDecl>(PD->getCanonicalDecl()); |
398 | 28 | PropertyToIvarMap[PD] = ID; |
399 | 28 | IvarToPopertyMap[ID] = PD; |
400 | 28 | |
401 | 28 | // Find the setter and the getter. |
402 | 28 | const ObjCMethodDecl *SetterD = PD->getSetterMethodDecl(); |
403 | 28 | if (SetterD) { |
404 | 26 | SetterD = SetterD->getCanonicalDecl(); |
405 | 26 | PropSetterToIvarMap[SetterD] = ID; |
406 | 26 | } |
407 | 28 | |
408 | 28 | const ObjCMethodDecl *GetterD = PD->getGetterMethodDecl(); |
409 | 28 | if (GetterD) { |
410 | 28 | GetterD = GetterD->getCanonicalDecl(); |
411 | 28 | PropGetterToIvarMap[GetterD] = ID; |
412 | 28 | } |
413 | 28 | } |
414 | 28 | |
415 | 28 | // If no ivars need invalidation, there is nothing to check here. |
416 | 28 | if (Ivars.empty()) |
417 | 4 | return; |
418 | 24 | |
419 | 24 | // Find all partial invalidation methods. |
420 | 24 | InvalidationInfo PartialInfo; |
421 | 24 | containsInvalidationMethod(InterfaceD, PartialInfo, /*LookForPartial*/ true); |
422 | 24 | |
423 | 24 | // Remove ivars invalidated by the partial invalidation methods. They do not |
424 | 24 | // need to be invalidated in the regular invalidation methods. |
425 | 24 | bool AtImplementationContainsAtLeastOnePartialInvalidationMethod = false; |
426 | 24 | for (MethodSet::iterator |
427 | 24 | I = PartialInfo.InvalidationMethods.begin(), |
428 | 42 | E = PartialInfo.InvalidationMethods.end(); I != E; ++I18 ) { |
429 | 18 | const ObjCMethodDecl *InterfD = *I; |
430 | 18 | |
431 | 18 | // Get the corresponding method in the @implementation. |
432 | 18 | const ObjCMethodDecl *D = ImplD->getMethod(InterfD->getSelector(), |
433 | 18 | InterfD->isInstanceMethod()); |
434 | 18 | if (D && D->hasBody()16 ) { |
435 | 16 | AtImplementationContainsAtLeastOnePartialInvalidationMethod = true; |
436 | 16 | |
437 | 16 | bool CalledAnotherInvalidationMethod = false; |
438 | 16 | // The MethodCrowler is going to remove the invalidated ivars. |
439 | 16 | MethodCrawler(Ivars, |
440 | 16 | CalledAnotherInvalidationMethod, |
441 | 16 | PropSetterToIvarMap, |
442 | 16 | PropGetterToIvarMap, |
443 | 16 | PropertyToIvarMap, |
444 | 16 | BR.getContext()).VisitStmt(D->getBody()); |
445 | 16 | // If another invalidation method was called, trust that full invalidation |
446 | 16 | // has occurred. |
447 | 16 | if (CalledAnotherInvalidationMethod) |
448 | 2 | Ivars.clear(); |
449 | 16 | } |
450 | 18 | } |
451 | 24 | |
452 | 24 | // If all ivars have been invalidated by partial invalidators, there is |
453 | 24 | // nothing to check here. |
454 | 24 | if (Ivars.empty()) |
455 | 4 | return; |
456 | 20 | |
457 | 20 | // Find all invalidation methods in this @interface declaration and parents. |
458 | 20 | InvalidationInfo Info; |
459 | 20 | containsInvalidationMethod(InterfaceD, Info, /*LookForPartial*/ false); |
460 | 20 | |
461 | 20 | // Report an error in case none of the invalidation methods are declared. |
462 | 20 | if (!Info.needsInvalidation() && !PartialInfo.needsInvalidation()6 ) { |
463 | 4 | if (Filter.check_MissingInvalidationMethod) |
464 | 2 | reportNoInvalidationMethod(Filter.checkName_MissingInvalidationMethod, |
465 | 2 | FirstIvarDecl, IvarToPopertyMap, InterfaceD, |
466 | 2 | /*MissingDeclaration*/ true); |
467 | 4 | // If there are no invalidation methods, there is no ivar validation work |
468 | 4 | // to be done. |
469 | 4 | return; |
470 | 4 | } |
471 | 16 | |
472 | 16 | // Only check if Ivars are invalidated when InstanceVariableInvalidation |
473 | 16 | // has been requested. |
474 | 16 | if (!Filter.check_InstanceVariableInvalidation) |
475 | 8 | return; |
476 | 8 | |
477 | 8 | // Check that all ivars are invalidated by the invalidation methods. |
478 | 8 | bool AtImplementationContainsAtLeastOneInvalidationMethod = false; |
479 | 8 | for (MethodSet::iterator I = Info.InvalidationMethods.begin(), |
480 | 24 | E = Info.InvalidationMethods.end(); I != E; ++I16 ) { |
481 | 16 | const ObjCMethodDecl *InterfD = *I; |
482 | 16 | |
483 | 16 | // Get the corresponding method in the @implementation. |
484 | 16 | const ObjCMethodDecl *D = ImplD->getMethod(InterfD->getSelector(), |
485 | 16 | InterfD->isInstanceMethod()); |
486 | 16 | if (D && D->hasBody()9 ) { |
487 | 9 | AtImplementationContainsAtLeastOneInvalidationMethod = true; |
488 | 9 | |
489 | 9 | // Get a copy of ivars needing invalidation. |
490 | 9 | IvarSet IvarsI = Ivars; |
491 | 9 | |
492 | 9 | bool CalledAnotherInvalidationMethod = false; |
493 | 9 | MethodCrawler(IvarsI, |
494 | 9 | CalledAnotherInvalidationMethod, |
495 | 9 | PropSetterToIvarMap, |
496 | 9 | PropGetterToIvarMap, |
497 | 9 | PropertyToIvarMap, |
498 | 9 | BR.getContext()).VisitStmt(D->getBody()); |
499 | 9 | // If another invalidation method was called, trust that full invalidation |
500 | 9 | // has occurred. |
501 | 9 | if (CalledAnotherInvalidationMethod) |
502 | 1 | continue; |
503 | 8 | |
504 | 8 | // Warn on the ivars that were not invalidated by the method. |
505 | 8 | for (IvarSet::const_iterator |
506 | 34 | I = IvarsI.begin(), E = IvarsI.end(); I != E; ++I26 ) |
507 | 26 | reportIvarNeedsInvalidation(I->first, IvarToPopertyMap, D); |
508 | 8 | } |
509 | 16 | } |
510 | 8 | |
511 | 8 | // Report an error in case none of the invalidation methods are implemented. |
512 | 8 | if (!AtImplementationContainsAtLeastOneInvalidationMethod) { |
513 | 4 | if (AtImplementationContainsAtLeastOnePartialInvalidationMethod) { |
514 | 1 | // Warn on the ivars that were not invalidated by the prrtial |
515 | 1 | // invalidation methods. |
516 | 1 | for (IvarSet::const_iterator |
517 | 3 | I = Ivars.begin(), E = Ivars.end(); I != E; ++I2 ) |
518 | 2 | reportIvarNeedsInvalidation(I->first, IvarToPopertyMap, nullptr); |
519 | 3 | } else { |
520 | 3 | // Otherwise, no invalidation methods were implemented. |
521 | 3 | reportNoInvalidationMethod(Filter.checkName_InstanceVariableInvalidation, |
522 | 3 | FirstIvarDecl, IvarToPopertyMap, InterfaceD, |
523 | 3 | /*MissingDeclaration*/ false); |
524 | 3 | } |
525 | 4 | } |
526 | 8 | } |
527 | | |
528 | | void IvarInvalidationCheckerImpl::reportNoInvalidationMethod( |
529 | | CheckName CheckName, const ObjCIvarDecl *FirstIvarDecl, |
530 | | const IvarToPropMapTy &IvarToPopertyMap, |
531 | 5 | const ObjCInterfaceDecl *InterfaceD, bool MissingDeclaration) const { |
532 | 5 | SmallString<128> sbuf; |
533 | 5 | llvm::raw_svector_ostream os(sbuf); |
534 | 5 | assert(FirstIvarDecl); |
535 | 5 | printIvar(os, FirstIvarDecl, IvarToPopertyMap); |
536 | 5 | os << "needs to be invalidated; "; |
537 | 5 | if (MissingDeclaration) |
538 | 2 | os << "no invalidation method is declared for "; |
539 | 3 | else |
540 | 3 | os << "no invalidation method is defined in the @implementation for "; |
541 | 5 | os << InterfaceD->getName(); |
542 | 5 | |
543 | 5 | PathDiagnosticLocation IvarDecLocation = |
544 | 5 | PathDiagnosticLocation::createBegin(FirstIvarDecl, BR.getSourceManager()); |
545 | 5 | |
546 | 5 | BR.EmitBasicReport(FirstIvarDecl, CheckName, "Incomplete invalidation", |
547 | 5 | categories::CoreFoundationObjectiveC, os.str(), |
548 | 5 | IvarDecLocation); |
549 | 5 | } |
550 | | |
551 | | void IvarInvalidationCheckerImpl:: |
552 | | reportIvarNeedsInvalidation(const ObjCIvarDecl *IvarD, |
553 | | const IvarToPropMapTy &IvarToPopertyMap, |
554 | 28 | const ObjCMethodDecl *MethodD) const { |
555 | 28 | SmallString<128> sbuf; |
556 | 28 | llvm::raw_svector_ostream os(sbuf); |
557 | 28 | printIvar(os, IvarD, IvarToPopertyMap); |
558 | 28 | os << "needs to be invalidated or set to nil"; |
559 | 28 | if (MethodD) { |
560 | 26 | PathDiagnosticLocation MethodDecLocation = |
561 | 26 | PathDiagnosticLocation::createEnd(MethodD->getBody(), |
562 | 26 | BR.getSourceManager(), |
563 | 26 | Mgr.getAnalysisDeclContext(MethodD)); |
564 | 26 | BR.EmitBasicReport(MethodD, Filter.checkName_InstanceVariableInvalidation, |
565 | 26 | "Incomplete invalidation", |
566 | 26 | categories::CoreFoundationObjectiveC, os.str(), |
567 | 26 | MethodDecLocation); |
568 | 26 | } else { |
569 | 2 | BR.EmitBasicReport( |
570 | 2 | IvarD, Filter.checkName_InstanceVariableInvalidation, |
571 | 2 | "Incomplete invalidation", categories::CoreFoundationObjectiveC, |
572 | 2 | os.str(), |
573 | 2 | PathDiagnosticLocation::createBegin(IvarD, BR.getSourceManager())); |
574 | 2 | } |
575 | 28 | } |
576 | | |
577 | | void IvarInvalidationCheckerImpl::MethodCrawler::markInvalidated( |
578 | 60 | const ObjCIvarDecl *Iv) { |
579 | 60 | IvarSet::iterator I = IVars.find(Iv); |
580 | 60 | if (I != IVars.end()) { |
581 | 51 | // If InvalidationMethod is present, we are processing the message send and |
582 | 51 | // should ensure we are invalidating with the appropriate method, |
583 | 51 | // otherwise, we are processing setting to 'nil'. |
584 | 51 | if (!InvalidationMethod || I->second.hasMethod(InvalidationMethod)26 ) |
585 | 48 | IVars.erase(I); |
586 | 51 | } |
587 | 60 | } |
588 | | |
589 | 96 | const Expr *IvarInvalidationCheckerImpl::MethodCrawler::peel(const Expr *E) const { |
590 | 96 | E = E->IgnoreParenCasts(); |
591 | 96 | if (const PseudoObjectExpr *POE = dyn_cast<PseudoObjectExpr>(E)) |
592 | 6 | E = POE->getSyntacticForm()->IgnoreParenCasts(); |
593 | 96 | if (const OpaqueValueExpr *OVE = dyn_cast<OpaqueValueExpr>(E)) |
594 | 24 | E = OVE->getSourceExpr()->IgnoreParenCasts(); |
595 | 96 | return E; |
596 | 96 | } |
597 | | |
598 | | void IvarInvalidationCheckerImpl::MethodCrawler::checkObjCIvarRefExpr( |
599 | 24 | const ObjCIvarRefExpr *IvarRef) { |
600 | 24 | if (const Decl *D = IvarRef->getDecl()) |
601 | 24 | markInvalidated(cast<ObjCIvarDecl>(D->getCanonicalDecl())); |
602 | 24 | } |
603 | | |
604 | | void IvarInvalidationCheckerImpl::MethodCrawler::checkObjCMessageExpr( |
605 | 6 | const ObjCMessageExpr *ME) { |
606 | 6 | const ObjCMethodDecl *MD = ME->getMethodDecl(); |
607 | 6 | if (MD) { |
608 | 6 | MD = MD->getCanonicalDecl(); |
609 | 6 | MethToIvarMapTy::const_iterator IvI = PropertyGetterToIvarMap.find(MD); |
610 | 6 | if (IvI != PropertyGetterToIvarMap.end()) |
611 | 6 | markInvalidated(IvI->second); |
612 | 6 | } |
613 | 6 | } |
614 | | |
615 | | void IvarInvalidationCheckerImpl::MethodCrawler::checkObjCPropertyRefExpr( |
616 | 15 | const ObjCPropertyRefExpr *PA) { |
617 | 15 | |
618 | 15 | if (PA->isExplicitProperty()) { |
619 | 15 | const ObjCPropertyDecl *PD = PA->getExplicitProperty(); |
620 | 15 | if (PD) { |
621 | 15 | PD = cast<ObjCPropertyDecl>(PD->getCanonicalDecl()); |
622 | 15 | PropToIvarMapTy::const_iterator IvI = PropertyToIvarMap.find(PD); |
623 | 15 | if (IvI != PropertyToIvarMap.end()) |
624 | 15 | markInvalidated(IvI->second); |
625 | 15 | return; |
626 | 15 | } |
627 | 0 | } |
628 | 0 | |
629 | 0 | if (PA->isImplicitProperty()) { |
630 | 0 | const ObjCMethodDecl *MD = PA->getImplicitPropertySetter(); |
631 | 0 | if (MD) { |
632 | 0 | MD = MD->getCanonicalDecl(); |
633 | 0 | MethToIvarMapTy::const_iterator IvI =PropertyGetterToIvarMap.find(MD); |
634 | 0 | if (IvI != PropertyGetterToIvarMap.end()) |
635 | 0 | markInvalidated(IvI->second); |
636 | 0 | return; |
637 | 0 | } |
638 | 0 | } |
639 | 0 | } |
640 | | |
641 | 35 | bool IvarInvalidationCheckerImpl::MethodCrawler::isZero(const Expr *E) const { |
642 | 35 | E = peel(E); |
643 | 35 | |
644 | 35 | return (E->isNullPointerConstant(Ctx, Expr::NPC_ValueDependentIsNotNull) |
645 | 35 | != Expr::NPCK_NotNull); |
646 | 35 | } |
647 | | |
648 | 61 | void IvarInvalidationCheckerImpl::MethodCrawler::check(const Expr *E) { |
649 | 61 | E = peel(E); |
650 | 61 | |
651 | 61 | if (const ObjCIvarRefExpr *IvarRef = dyn_cast<ObjCIvarRefExpr>(E)) { |
652 | 24 | checkObjCIvarRefExpr(IvarRef); |
653 | 24 | return; |
654 | 24 | } |
655 | 37 | |
656 | 37 | if (const ObjCPropertyRefExpr *PropRef = dyn_cast<ObjCPropertyRefExpr>(E)) { |
657 | 15 | checkObjCPropertyRefExpr(PropRef); |
658 | 15 | return; |
659 | 15 | } |
660 | 22 | |
661 | 22 | if (const ObjCMessageExpr *MsgExpr = dyn_cast<ObjCMessageExpr>(E)) { |
662 | 6 | checkObjCMessageExpr(MsgExpr); |
663 | 6 | return; |
664 | 6 | } |
665 | 22 | } |
666 | | |
667 | | void IvarInvalidationCheckerImpl::MethodCrawler::VisitBinaryOperator( |
668 | 19 | const BinaryOperator *BO) { |
669 | 19 | VisitStmt(BO); |
670 | 19 | |
671 | 19 | // Do we assign/compare against zero? If yes, check the variable we are |
672 | 19 | // assigning to. |
673 | 19 | BinaryOperatorKind Opcode = BO->getOpcode(); |
674 | 19 | if (Opcode != BO_Assign && |
675 | 19 | Opcode != BO_EQ3 && |
676 | 19 | Opcode != BO_NE0 ) |
677 | 0 | return; |
678 | 19 | |
679 | 19 | if (isZero(BO->getRHS())) { |
680 | 18 | check(BO->getLHS()); |
681 | 18 | return; |
682 | 18 | } |
683 | 1 | |
684 | 1 | if (Opcode != BO_Assign && isZero(BO->getLHS())) { |
685 | 1 | check(BO->getRHS()); |
686 | 1 | return; |
687 | 1 | } |
688 | 1 | } |
689 | | |
690 | | void IvarInvalidationCheckerImpl::MethodCrawler::VisitObjCMessageExpr( |
691 | 63 | const ObjCMessageExpr *ME) { |
692 | 63 | const ObjCMethodDecl *MD = ME->getMethodDecl(); |
693 | 63 | const Expr *Receiver = ME->getInstanceReceiver(); |
694 | 63 | |
695 | 63 | // Stop if we are calling '[self invalidate]'. |
696 | 63 | if (Receiver && isInvalidationMethod(MD, /*LookForPartial*/ false)60 ) |
697 | 26 | if (Receiver->isObjCSelfExpr()) { |
698 | 3 | CalledAnotherInvalidationMethod = true; |
699 | 3 | return; |
700 | 3 | } |
701 | 60 | |
702 | 60 | // Check if we call a setter and set the property to 'nil'. |
703 | 60 | if (MD && (ME->getNumArgs() == 1) && isZero(ME->getArg(0))15 ) { |
704 | 15 | MD = MD->getCanonicalDecl(); |
705 | 15 | MethToIvarMapTy::const_iterator IvI = PropertySetterToIvarMap.find(MD); |
706 | 15 | if (IvI != PropertySetterToIvarMap.end()) { |
707 | 15 | markInvalidated(IvI->second); |
708 | 15 | return; |
709 | 15 | } |
710 | 45 | } |
711 | 45 | |
712 | 45 | // Check if we call the 'invalidation' routine on the ivar. |
713 | 45 | if (Receiver) { |
714 | 42 | InvalidationMethod = MD; |
715 | 42 | check(Receiver->IgnoreParenCasts()); |
716 | 42 | InvalidationMethod = nullptr; |
717 | 42 | } |
718 | 45 | |
719 | 45 | VisitStmt(ME); |
720 | 45 | } |
721 | | } // end anonymous namespace |
722 | | |
723 | | // Register the checkers. |
724 | | namespace { |
725 | | class IvarInvalidationChecker : |
726 | | public Checker<check::ASTDecl<ObjCImplementationDecl> > { |
727 | | public: |
728 | | ChecksFilter Filter; |
729 | | public: |
730 | | void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager& Mgr, |
731 | 28 | BugReporter &BR) const { |
732 | 28 | IvarInvalidationCheckerImpl Walker(Mgr, BR, Filter); |
733 | 28 | Walker.visit(D); |
734 | 28 | } |
735 | | }; |
736 | | } // end anonymous namespace |
737 | | |
738 | 3 | void ento::registerIvarInvalidationModeling(CheckerManager &mgr) { |
739 | 3 | mgr.registerChecker<IvarInvalidationChecker>(); |
740 | 3 | } |
741 | | |
742 | 1 | bool ento::shouldRegisterIvarInvalidationModeling(const LangOptions &LO) { |
743 | 1 | return true; |
744 | 1 | } |
745 | | |
746 | | #define REGISTER_CHECKER(name) \ |
747 | 4 | void ento::register##name(CheckerManager &mgr) { \ |
748 | 4 | IvarInvalidationChecker *checker = \ |
749 | 4 | mgr.getChecker<IvarInvalidationChecker>(); \ |
750 | 4 | checker->Filter.check_##name = true; \ |
751 | 4 | checker->Filter.checkName_##name = mgr.getCurrentCheckName(); \ |
752 | 4 | } \ clang::ento::registerInstanceVariableInvalidation(clang::ento::CheckerManager&) Line | Count | Source | 747 | 2 | void ento::register##name(CheckerManager &mgr) { \ | 748 | 2 | IvarInvalidationChecker *checker = \ | 749 | 2 | mgr.getChecker<IvarInvalidationChecker>(); \ | 750 | 2 | checker->Filter.check_##name = true; \ | 751 | 2 | checker->Filter.checkName_##name = mgr.getCurrentCheckName(); \ | 752 | 2 | } \ |
clang::ento::registerMissingInvalidationMethod(clang::ento::CheckerManager&) Line | Count | Source | 747 | 2 | void ento::register##name(CheckerManager &mgr) { \ | 748 | 2 | IvarInvalidationChecker *checker = \ | 749 | 2 | mgr.getChecker<IvarInvalidationChecker>(); \ | 750 | 2 | checker->Filter.check_##name = true; \ | 751 | 2 | checker->Filter.checkName_##name = mgr.getCurrentCheckName(); \ | 752 | 2 | } \ |
|
753 | | \ |
754 | 4 | bool ento::shouldRegister##name(const LangOptions &LO) { \ |
755 | 4 | return true; \ |
756 | 4 | } clang::ento::shouldRegisterInstanceVariableInvalidation(clang::LangOptions const&) Line | Count | Source | 754 | 2 | bool ento::shouldRegister##name(const LangOptions &LO) { \ | 755 | 2 | return true; \ | 756 | 2 | } |
clang::ento::shouldRegisterMissingInvalidationMethod(clang::LangOptions const&) Line | Count | Source | 754 | 2 | bool ento::shouldRegister##name(const LangOptions &LO) { \ | 755 | 2 | return true; \ | 756 | 2 | } |
|
757 | | |
758 | | REGISTER_CHECKER(InstanceVariableInvalidation) |
759 | | REGISTER_CHECKER(MissingInvalidationMethod) |