xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp (revision 5f37643de173dc9c63b9ed009d067250361b08dc)
1 //=- IvarInvalidationChecker.cpp - -*- C++ -------------------------------*-==//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 //
10 //  This checker implements annotation driven invalidation checking. If a class
11 //  contains a method annotated with 'objc_instance_variable_invalidator',
12 //  - (void) foo
13 //           __attribute__((annotate("objc_instance_variable_invalidator")));
14 //  all the "ivalidatable" instance variables of this class should be
15 //  invalidated. We call an instance variable ivalidatable if it is an object of
16 //  a class which contains an invalidation method. There could be multiple
17 //  methods annotated with such annotations per class, either one can be used
18 //  to invalidate the ivar. An ivar or property are considered to be
19 //  invalidated if they are being assigned 'nil' or an invalidation method has
20 //  been called on them. An invalidation method should either invalidate all
21 //  the ivars or call another invalidation method (on self).
22 //
23 //===----------------------------------------------------------------------===//
24 
25 #include "ClangSACheckers.h"
26 #include "clang/AST/Attr.h"
27 #include "clang/AST/DeclObjC.h"
28 #include "clang/AST/StmtVisitor.h"
29 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
30 #include "clang/StaticAnalyzer/Core/Checker.h"
31 #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
32 #include "llvm/ADT/DenseMap.h"
33 #include "llvm/ADT/SmallString.h"
34 
35 using namespace clang;
36 using namespace ento;
37 
38 namespace {
39 class IvarInvalidationChecker :
40   public Checker<check::ASTDecl<ObjCMethodDecl> > {
41 
42   typedef llvm::DenseSet<const ObjCMethodDecl*> MethodSet;
43   typedef llvm::DenseMap<const ObjCMethodDecl*,
44                          const ObjCIvarDecl*> MethToIvarMapTy;
45   typedef llvm::DenseMap<const ObjCPropertyDecl*,
46                          const ObjCIvarDecl*> PropToIvarMapTy;
47   typedef llvm::DenseMap<const ObjCIvarDecl*,
48                          const ObjCPropertyDecl*> IvarToPropMapTy;
49 
50 
51   struct IvarInfo {
52     /// Has the ivar been invalidated?
53     bool IsInvalidated;
54 
55     /// The methods which can be used to invalidate the ivar.
56     MethodSet InvalidationMethods;
57 
58     IvarInfo() : IsInvalidated(false) {}
59     void addInvalidationMethod(const ObjCMethodDecl *MD) {
60       InvalidationMethods.insert(MD);
61     }
62 
63     bool needsInvalidation() const {
64       return !InvalidationMethods.empty();
65     }
66 
67     void markInvalidated() {
68       IsInvalidated = true;
69     }
70 
71     bool markInvalidated(const ObjCMethodDecl *MD) {
72       if (IsInvalidated)
73         return true;
74       for (MethodSet::iterator I = InvalidationMethods.begin(),
75           E = InvalidationMethods.end(); I != E; ++I) {
76         if (*I == MD) {
77           IsInvalidated = true;
78           return true;
79         }
80       }
81       return false;
82     }
83 
84     bool isInvalidated() const {
85       return IsInvalidated;
86     }
87   };
88 
89   typedef llvm::DenseMap<const ObjCIvarDecl*, IvarInfo> IvarSet;
90 
91   /// Statement visitor, which walks the method body and flags the ivars
92   /// referenced in it (either directly or via property).
93   class MethodCrawler : public ConstStmtVisitor<MethodCrawler> {
94     /// The set of Ivars which need to be invalidated.
95     IvarSet &IVars;
96 
97     /// Flag is set as the result of a message send to another
98     /// invalidation method.
99     bool &CalledAnotherInvalidationMethod;
100 
101     /// Property setter to ivar mapping.
102     const MethToIvarMapTy &PropertySetterToIvarMap;
103 
104     /// Property getter to ivar mapping.
105     const MethToIvarMapTy &PropertyGetterToIvarMap;
106 
107     /// Property to ivar mapping.
108     const PropToIvarMapTy &PropertyToIvarMap;
109 
110     /// The invalidation method being currently processed.
111     const ObjCMethodDecl *InvalidationMethod;
112 
113     ASTContext &Ctx;
114 
115     /// Peel off parens, casts, OpaqueValueExpr, and PseudoObjectExpr.
116     const Expr *peel(const Expr *E) const;
117 
118     /// Does this expression represent zero: '0'?
119     bool isZero(const Expr *E) const;
120 
121     /// Mark the given ivar as invalidated.
122     void markInvalidated(const ObjCIvarDecl *Iv);
123 
124     /// Checks if IvarRef refers to the tracked IVar, if yes, marks it as
125     /// invalidated.
126     void checkObjCIvarRefExpr(const ObjCIvarRefExpr *IvarRef);
127 
128     /// Checks if ObjCPropertyRefExpr refers to the tracked IVar, if yes, marks
129     /// it as invalidated.
130     void checkObjCPropertyRefExpr(const ObjCPropertyRefExpr *PA);
131 
132     /// Checks if ObjCMessageExpr refers to (is a getter for) the tracked IVar,
133     /// if yes, marks it as invalidated.
134     void checkObjCMessageExpr(const ObjCMessageExpr *ME);
135 
136     /// Checks if the Expr refers to an ivar, if yes, marks it as invalidated.
137     void check(const Expr *E);
138 
139   public:
140     MethodCrawler(IvarSet &InIVars,
141                   bool &InCalledAnotherInvalidationMethod,
142                   const MethToIvarMapTy &InPropertySetterToIvarMap,
143                   const MethToIvarMapTy &InPropertyGetterToIvarMap,
144                   const PropToIvarMapTy &InPropertyToIvarMap,
145                   ASTContext &InCtx)
146     : IVars(InIVars),
147       CalledAnotherInvalidationMethod(InCalledAnotherInvalidationMethod),
148       PropertySetterToIvarMap(InPropertySetterToIvarMap),
149       PropertyGetterToIvarMap(InPropertyGetterToIvarMap),
150       PropertyToIvarMap(InPropertyToIvarMap),
151       InvalidationMethod(0),
152       Ctx(InCtx) {}
153 
154     void VisitStmt(const Stmt *S) { VisitChildren(S); }
155 
156     void VisitBinaryOperator(const BinaryOperator *BO);
157 
158     void VisitObjCMessageExpr(const ObjCMessageExpr *ME);
159 
160     void VisitChildren(const Stmt *S) {
161       for (Stmt::const_child_range I = S->children(); I; ++I) {
162         if (*I)
163           this->Visit(*I);
164         if (CalledAnotherInvalidationMethod)
165           return;
166       }
167     }
168   };
169 
170   /// Check if the any of the methods inside the interface are annotated with
171   /// the invalidation annotation, update the IvarInfo accordingly.
172   static void containsInvalidationMethod(const ObjCContainerDecl *D,
173                                          IvarInfo &Out);
174 
175   /// Check if ivar should be tracked and add to TrackedIvars if positive.
176   /// Returns true if ivar should be tracked.
177   static bool trackIvar(const ObjCIvarDecl *Iv, IvarSet &TrackedIvars);
178 
179   /// Given the property declaration, and the list of tracked ivars, finds
180   /// the ivar backing the property when possible. Returns '0' when no such
181   /// ivar could be found.
182   static const ObjCIvarDecl *findPropertyBackingIvar(
183       const ObjCPropertyDecl *Prop,
184       const ObjCInterfaceDecl *InterfaceD,
185       IvarSet &TrackedIvars);
186 
187 public:
188   void checkASTDecl(const ObjCMethodDecl *D, AnalysisManager& Mgr,
189                     BugReporter &BR) const;
190 
191   // TODO: We are currently ignoring the ivars coming from class extensions.
192 };
193 
194 static bool isInvalidationMethod(const ObjCMethodDecl *M) {
195   for (specific_attr_iterator<AnnotateAttr>
196        AI = M->specific_attr_begin<AnnotateAttr>(),
197        AE = M->specific_attr_end<AnnotateAttr>(); AI != AE; ++AI) {
198     const AnnotateAttr *Ann = *AI;
199     if (Ann->getAnnotation() == "objc_instance_variable_invalidator")
200       return true;
201   }
202   return false;
203 }
204 
205 void IvarInvalidationChecker::containsInvalidationMethod(
206     const ObjCContainerDecl *D, IvarInfo &OutInfo) {
207 
208   // TODO: Cache the results.
209 
210   if (!D)
211     return;
212 
213   // Check all methods.
214   for (ObjCContainerDecl::method_iterator
215       I = D->meth_begin(),
216       E = D->meth_end(); I != E; ++I) {
217       const ObjCMethodDecl *MDI = *I;
218       if (isInvalidationMethod(MDI))
219         OutInfo.addInvalidationMethod(
220                                cast<ObjCMethodDecl>(MDI->getCanonicalDecl()));
221   }
222 
223   // If interface, check all parent protocols and super.
224   // TODO: Visit all categories in case the invalidation method is declared in
225   // a category.
226   if (const ObjCInterfaceDecl *InterfaceD = dyn_cast<ObjCInterfaceDecl>(D)) {
227     for (ObjCInterfaceDecl::protocol_iterator
228         I = InterfaceD->protocol_begin(),
229         E = InterfaceD->protocol_end(); I != E; ++I) {
230       containsInvalidationMethod(*I, OutInfo);
231     }
232     containsInvalidationMethod(InterfaceD->getSuperClass(), OutInfo);
233     return;
234   }
235 
236   // If protocol, check all parent protocols.
237   if (const ObjCProtocolDecl *ProtD = dyn_cast<ObjCProtocolDecl>(D)) {
238     for (ObjCInterfaceDecl::protocol_iterator
239         I = ProtD->protocol_begin(),
240         E = ProtD->protocol_end(); I != E; ++I) {
241       containsInvalidationMethod(*I, OutInfo);
242     }
243     return;
244   }
245 
246   llvm_unreachable("One of the casts above should have succeeded.");
247 }
248 
249 bool IvarInvalidationChecker::trackIvar(const ObjCIvarDecl *Iv,
250                                         IvarSet &TrackedIvars) {
251   QualType IvQTy = Iv->getType();
252   const ObjCObjectPointerType *IvTy = IvQTy->getAs<ObjCObjectPointerType>();
253   if (!IvTy)
254     return false;
255   const ObjCInterfaceDecl *IvInterf = IvTy->getInterfaceDecl();
256 
257   IvarInfo Info;
258   containsInvalidationMethod(IvInterf, Info);
259   if (Info.needsInvalidation()) {
260     TrackedIvars[cast<ObjCIvarDecl>(Iv->getCanonicalDecl())] = Info;
261     return true;
262   }
263   return false;
264 }
265 
266 const ObjCIvarDecl *IvarInvalidationChecker::findPropertyBackingIvar(
267                         const ObjCPropertyDecl *Prop,
268                         const ObjCInterfaceDecl *InterfaceD,
269                         IvarSet &TrackedIvars) {
270   const ObjCIvarDecl *IvarD = 0;
271 
272   // Lookup for the synthesized case.
273   IvarD = Prop->getPropertyIvarDecl();
274   // We only track the ivars/properties that are defined in the current
275   // class (not the parent).
276   if (IvarD && IvarD->getContainingInterface() == InterfaceD) {
277     if (TrackedIvars.count(IvarD)) {
278       return IvarD;
279     }
280     // If the ivar is synthesized we still want to track it.
281     if (trackIvar(IvarD, TrackedIvars))
282       return IvarD;
283   }
284 
285   // Lookup IVars named "_PropName"or "PropName" among the tracked Ivars.
286   StringRef PropName = Prop->getIdentifier()->getName();
287   for (IvarSet::const_iterator I = TrackedIvars.begin(),
288                                E = TrackedIvars.end(); I != E; ++I) {
289     const ObjCIvarDecl *Iv = I->first;
290     StringRef IvarName = Iv->getName();
291 
292     if (IvarName == PropName)
293       return Iv;
294 
295     SmallString<128> PropNameWithUnderscore;
296     {
297       llvm::raw_svector_ostream os(PropNameWithUnderscore);
298       os << '_' << PropName;
299     }
300     if (IvarName == PropNameWithUnderscore.str())
301       return Iv;
302   }
303 
304   // Note, this is a possible source of false positives. We could look at the
305   // getter implementation to find the ivar when its name is not derived from
306   // the property name.
307   return 0;
308 }
309 
310 void IvarInvalidationChecker::checkASTDecl(const ObjCMethodDecl *D,
311                                           AnalysisManager& Mgr,
312                                           BugReporter &BR) const {
313   // We are only interested in checking the cleanup methods.
314   if (!D->hasBody() || !isInvalidationMethod(D))
315     return;
316 
317   // Collect all ivars that need cleanup.
318   IvarSet Ivars;
319   const ObjCInterfaceDecl *InterfaceD = D->getClassInterface();
320 
321   // Collect ivars declared in this class, its extensions and its implementation
322   ObjCInterfaceDecl *IDecl = const_cast<ObjCInterfaceDecl *>(InterfaceD);
323   for (const ObjCIvarDecl *Iv = IDecl->all_declared_ivar_begin(); Iv;
324        Iv= Iv->getNextIvar())
325     trackIvar(Iv, Ivars);
326 
327   // Construct Property/Property Accessor to Ivar maps to assist checking if an
328   // ivar which is backing a property has been reset.
329   MethToIvarMapTy PropSetterToIvarMap;
330   MethToIvarMapTy PropGetterToIvarMap;
331   PropToIvarMapTy PropertyToIvarMap;
332   IvarToPropMapTy IvarToPopertyMap;
333 
334   ObjCInterfaceDecl::PropertyMap PropMap;
335   InterfaceD->collectPropertiesToImplement(PropMap);
336 
337   for (ObjCInterfaceDecl::PropertyMap::iterator
338       I = PropMap.begin(), E = PropMap.end(); I != E; ++I) {
339     const ObjCPropertyDecl *PD = I->second;
340 
341     const ObjCIvarDecl *ID = findPropertyBackingIvar(PD, InterfaceD, Ivars);
342     if (!ID) {
343       continue;
344     }
345 
346     // Store the mappings.
347     PD = cast<ObjCPropertyDecl>(PD->getCanonicalDecl());
348     PropertyToIvarMap[PD] = ID;
349     IvarToPopertyMap[ID] = PD;
350 
351     // Find the setter and the getter.
352     const ObjCMethodDecl *SetterD = PD->getSetterMethodDecl();
353     if (SetterD) {
354       SetterD = cast<ObjCMethodDecl>(SetterD->getCanonicalDecl());
355       PropSetterToIvarMap[SetterD] = ID;
356     }
357 
358     const ObjCMethodDecl *GetterD = PD->getGetterMethodDecl();
359     if (GetterD) {
360       GetterD = cast<ObjCMethodDecl>(GetterD->getCanonicalDecl());
361       PropGetterToIvarMap[GetterD] = ID;
362     }
363   }
364 
365 
366   // Check which ivars have been invalidated in the method body.
367   bool CalledAnotherInvalidationMethod = false;
368   MethodCrawler(Ivars,
369                 CalledAnotherInvalidationMethod,
370                 PropSetterToIvarMap,
371                 PropGetterToIvarMap,
372                 PropertyToIvarMap,
373                 BR.getContext()).VisitStmt(D->getBody());
374 
375   if (CalledAnotherInvalidationMethod)
376     return;
377 
378   // Warn on the ivars that were not accessed by the method.
379   for (IvarSet::const_iterator I = Ivars.begin(), E = Ivars.end(); I != E; ++I){
380     if (!I->second.isInvalidated()) {
381       const ObjCIvarDecl *IvarDecl = I->first;
382 
383       PathDiagnosticLocation IvarDecLocation =
384           PathDiagnosticLocation::createEnd(D->getBody(), BR.getSourceManager(),
385                                             Mgr.getAnalysisDeclContext(D));
386 
387       SmallString<128> sbuf;
388       llvm::raw_svector_ostream os(sbuf);
389 
390       // Construct the warning message.
391       if (IvarDecl->getSynthesize()) {
392         const ObjCPropertyDecl *PD = IvarToPopertyMap[IvarDecl];
393         assert(PD &&
394                "Do we synthesize ivars for something other than properties?");
395         os << "Property "<< PD->getName() <<
396               " needs to be invalidated or set to nil";
397       } else {
398         os << "Instance variable "<< IvarDecl->getName()
399              << " needs to be invalidated or set to nil";
400       }
401 
402       BR.EmitBasicReport(D,
403           "Incomplete invalidation",
404           categories::CoreFoundationObjectiveC, os.str(),
405           IvarDecLocation);
406     }
407   }
408 }
409 
410 void IvarInvalidationChecker::MethodCrawler::markInvalidated(
411     const ObjCIvarDecl *Iv) {
412   IvarSet::iterator I = IVars.find(Iv);
413   if (I != IVars.end()) {
414     // If InvalidationMethod is present, we are processing the message send and
415     // should ensure we are invalidating with the appropriate method,
416     // otherwise, we are processing setting to 'nil'.
417     if (InvalidationMethod)
418       I->second.markInvalidated(InvalidationMethod);
419     else
420       I->second.markInvalidated();
421   }
422 }
423 
424 const Expr *IvarInvalidationChecker::MethodCrawler::peel(const Expr *E) const {
425   E = E->IgnoreParenCasts();
426   if (const PseudoObjectExpr *POE = dyn_cast<PseudoObjectExpr>(E))
427     E = POE->getSyntacticForm()->IgnoreParenCasts();
428   if (const OpaqueValueExpr *OVE = dyn_cast<OpaqueValueExpr>(E))
429     E = OVE->getSourceExpr()->IgnoreParenCasts();
430   return E;
431 }
432 
433 void IvarInvalidationChecker::MethodCrawler::checkObjCIvarRefExpr(
434     const ObjCIvarRefExpr *IvarRef) {
435   if (const Decl *D = IvarRef->getDecl())
436     markInvalidated(cast<ObjCIvarDecl>(D->getCanonicalDecl()));
437 }
438 
439 void IvarInvalidationChecker::MethodCrawler::checkObjCMessageExpr(
440     const ObjCMessageExpr *ME) {
441   const ObjCMethodDecl *MD = ME->getMethodDecl();
442   if (MD) {
443     MD = cast<ObjCMethodDecl>(MD->getCanonicalDecl());
444     MethToIvarMapTy::const_iterator IvI = PropertyGetterToIvarMap.find(MD);
445     if (IvI != PropertyGetterToIvarMap.end())
446       markInvalidated(IvI->second);
447   }
448 }
449 
450 void IvarInvalidationChecker::MethodCrawler::checkObjCPropertyRefExpr(
451     const ObjCPropertyRefExpr *PA) {
452 
453   if (PA->isExplicitProperty()) {
454     const ObjCPropertyDecl *PD = PA->getExplicitProperty();
455     if (PD) {
456       PD = cast<ObjCPropertyDecl>(PD->getCanonicalDecl());
457       PropToIvarMapTy::const_iterator IvI = PropertyToIvarMap.find(PD);
458       if (IvI != PropertyToIvarMap.end())
459         markInvalidated(IvI->second);
460       return;
461     }
462   }
463 
464   if (PA->isImplicitProperty()) {
465     const ObjCMethodDecl *MD = PA->getImplicitPropertySetter();
466     if (MD) {
467       MD = cast<ObjCMethodDecl>(MD->getCanonicalDecl());
468       MethToIvarMapTy::const_iterator IvI =PropertyGetterToIvarMap.find(MD);
469       if (IvI != PropertyGetterToIvarMap.end())
470         markInvalidated(IvI->second);
471       return;
472     }
473   }
474 }
475 
476 bool IvarInvalidationChecker::MethodCrawler::isZero(const Expr *E) const {
477   E = peel(E);
478 
479   return (E->isNullPointerConstant(Ctx, Expr::NPC_ValueDependentIsNotNull)
480            != Expr::NPCK_NotNull);
481 }
482 
483 void IvarInvalidationChecker::MethodCrawler::check(const Expr *E) {
484   E = peel(E);
485 
486   if (const ObjCIvarRefExpr *IvarRef = dyn_cast<ObjCIvarRefExpr>(E)) {
487     checkObjCIvarRefExpr(IvarRef);
488     return;
489   }
490 
491   if (const ObjCPropertyRefExpr *PropRef = dyn_cast<ObjCPropertyRefExpr>(E)) {
492     checkObjCPropertyRefExpr(PropRef);
493     return;
494   }
495 
496   if (const ObjCMessageExpr *MsgExpr = dyn_cast<ObjCMessageExpr>(E)) {
497     checkObjCMessageExpr(MsgExpr);
498     return;
499   }
500 }
501 
502 void IvarInvalidationChecker::MethodCrawler::VisitBinaryOperator(
503     const BinaryOperator *BO) {
504   VisitStmt(BO);
505 
506   if (BO->getOpcode() != BO_Assign)
507     return;
508 
509   // Do we assign zero?
510   if (!isZero(BO->getRHS()))
511     return;
512 
513   // Check the variable we are assigning to.
514   check(BO->getLHS());
515 }
516 
517 void IvarInvalidationChecker::MethodCrawler::VisitObjCMessageExpr(
518     const ObjCMessageExpr *ME) {
519   const ObjCMethodDecl *MD = ME->getMethodDecl();
520   const Expr *Receiver = ME->getInstanceReceiver();
521 
522   // Stop if we are calling '[self invalidate]'.
523   if (Receiver && isInvalidationMethod(MD))
524     if (Receiver->isObjCSelfExpr()) {
525       CalledAnotherInvalidationMethod = true;
526       return;
527     }
528 
529   // Check if we call a setter and set the property to 'nil'.
530   if (MD && (ME->getNumArgs() == 1) && isZero(ME->getArg(0))) {
531     MD = cast<ObjCMethodDecl>(MD->getCanonicalDecl());
532     MethToIvarMapTy::const_iterator IvI = PropertySetterToIvarMap.find(MD);
533     if (IvI != PropertySetterToIvarMap.end()) {
534       markInvalidated(IvI->second);
535       return;
536     }
537   }
538 
539   // Check if we call the 'invalidation' routine on the ivar.
540   if (Receiver) {
541     InvalidationMethod = MD;
542     check(Receiver->IgnoreParenCasts());
543     InvalidationMethod = 0;
544   }
545 
546   VisitStmt(ME);
547 }
548 }
549 
550 // Register the checker.
551 void ento::registerIvarInvalidationChecker(CheckerManager &mgr) {
552   mgr.registerChecker<IvarInvalidationChecker>();
553 }
554