xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp (revision 9802f9fb2a64df1c27251a3284394c2748122128)
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.
17 //
18 //  Note, this checker currently only checks if an ivar was accessed by the
19 //  method, we do not currently support any deeper invalidation checking.
20 //
21 //===----------------------------------------------------------------------===//
22 
23 #include "ClangSACheckers.h"
24 #include "clang/StaticAnalyzer/Core/Checker.h"
25 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
26 #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
27 #include "clang/AST/DeclObjC.h"
28 #include "clang/AST/StmtVisitor.h"
29 #include "llvm/ADT/DenseMap.h"
30 #include "llvm/ADT/SmallString.h"
31 
32 using namespace clang;
33 using namespace ento;
34 
35 namespace {
36 class IvarInvalidationChecker :
37   public Checker<check::ASTDecl<ObjCMethodDecl> > {
38 
39   typedef llvm::DenseMap<const ObjCIvarDecl*, bool> IvarSet;
40   typedef llvm::DenseMap<const ObjCMethodDecl*,
41                          const ObjCIvarDecl*> MethToIvarMapTy;
42   typedef llvm::DenseMap<const ObjCPropertyDecl*,
43                          const ObjCIvarDecl*> PropToIvarMapTy;
44 
45   /// Statement visitor, which walks the method body and flags the ivars
46   /// referenced in it (either directly or via property).
47   class MethodCrawler : public ConstStmtVisitor<MethodCrawler> {
48     const ObjCInterfaceDecl *InterfD;
49 
50     /// The set of Ivars which need to be invalidated.
51     IvarSet &IVars;
52 
53     /// Property setter to ivar mapping.
54     MethToIvarMapTy &PropertySetterToIvarMap;
55 
56     // Property to ivar mapping.
57     PropToIvarMapTy &PropertyToIvarMap;
58 
59   public:
60     MethodCrawler(const ObjCInterfaceDecl *InID,
61                   IvarSet &InIVars, MethToIvarMapTy &InPropertySetterToIvarMap,
62                   PropToIvarMapTy &InPropertyToIvarMap)
63     : InterfD(InID), IVars(InIVars),
64       PropertySetterToIvarMap(InPropertySetterToIvarMap),
65       PropertyToIvarMap(InPropertyToIvarMap) {}
66 
67     void VisitStmt(const Stmt *S) { VisitChildren(S); }
68 
69     void VisitObjCIvarRefExpr(const ObjCIvarRefExpr *IvarRef);
70 
71     void VisitObjCMessageExpr(const ObjCMessageExpr *ME);
72 
73     void VisitObjCPropertyRefExpr(const ObjCPropertyRefExpr *PA);
74 
75     void VisitChildren(const Stmt *S) {
76       for (Stmt::const_child_range I = S->children(); I; ++I)
77         if (*I)
78           static_cast<MethodCrawler*>(this)->Visit(*I);
79     }
80   };
81 
82   /// Check if the any of the methods inside the interface are annotated with
83   /// the invalidation annotation.
84   bool containsInvalidationMethod(const ObjCContainerDecl *D) const;
85 
86   /// Given the property declaration, and the list of tracked ivars, finds
87   /// the ivar backing the property when possible. Returns '0' when no such
88   /// ivar could be found.
89   static const ObjCIvarDecl *findPropertyBackingIvar(
90       const ObjCPropertyDecl *Prop,
91       const ObjCInterfaceDecl *InterfaceD,
92       IvarSet TrackedIvars);
93 
94 public:
95   void checkASTDecl(const ObjCMethodDecl *D, AnalysisManager& Mgr,
96                     BugReporter &BR) const;
97 
98 };
99 
100 bool isInvalidationMethod(const ObjCMethodDecl *M) {
101   const AnnotateAttr *Ann = M->getAttr<AnnotateAttr>();
102   if (!Ann)
103     return false;
104   if (Ann->getAnnotation() == "objc_instance_variable_invalidator")
105     return true;
106   return false;
107 }
108 
109 bool IvarInvalidationChecker::containsInvalidationMethod (
110     const ObjCContainerDecl *D) const {
111 
112   // TODO: Cache the results.
113 
114   if (!D)
115     return false;
116 
117   // Check all methods.
118   for (ObjCContainerDecl::method_iterator
119       I = D->meth_begin(),
120       E = D->meth_end(); I != E; ++I) {
121       const ObjCMethodDecl *MDI = *I;
122       if (isInvalidationMethod(MDI))
123         return true;
124   }
125 
126   // If interface, check all parent protocols and super.
127   // TODO: Visit all categories in case the invalidation method is declared in
128   // a category.
129   if (const ObjCInterfaceDecl *InterfaceD = dyn_cast<ObjCInterfaceDecl>(D)) {
130     for (ObjCInterfaceDecl::protocol_iterator
131         I = InterfaceD->protocol_begin(),
132         E = InterfaceD->protocol_end(); I != E; ++I) {
133       if (containsInvalidationMethod(*I))
134         return true;
135     }
136     return containsInvalidationMethod(InterfaceD->getSuperClass());
137   }
138 
139   // If protocol, check all parent protocols.
140   if (const ObjCProtocolDecl *ProtD = dyn_cast<ObjCProtocolDecl>(D)) {
141     for (ObjCInterfaceDecl::protocol_iterator
142         I = ProtD->protocol_begin(),
143         E = ProtD->protocol_end(); I != E; ++I) {
144       if (containsInvalidationMethod(*I))
145         return true;
146     }
147     return false;
148   }
149 
150   llvm_unreachable("One of the casts above should have succeeded.");
151 }
152 
153 const ObjCIvarDecl *IvarInvalidationChecker::findPropertyBackingIvar(
154                         const ObjCPropertyDecl *Prop,
155                         const ObjCInterfaceDecl *InterfaceD,
156                         IvarSet TrackedIvars) {
157   const ObjCIvarDecl *IvarD = 0;
158 
159   // Lookup for the synthesized case.
160   IvarD = Prop->getPropertyIvarDecl();
161   if (IvarD)
162     return IvarD;
163 
164   // Lookup IVars named "_PropName"or "PropName" among the tracked Ivars.
165   StringRef PropName = Prop->getIdentifier()->getName();
166   for (IvarSet::const_iterator I = TrackedIvars.begin(),
167                                E = TrackedIvars.end(); I != E; ++I) {
168     const ObjCIvarDecl *Iv = I->first;
169     StringRef IvarName = Iv->getName();
170 
171     if (IvarName == PropName)
172       return Iv;
173 
174     SmallString<128> PropNameWithUnderscore;
175     {
176       llvm::raw_svector_ostream os(PropNameWithUnderscore);
177       os << '_' << PropName;
178     }
179     if (IvarName == PropNameWithUnderscore.str())
180       return Iv;
181   }
182 
183   // Note, this is a possible source of false positives. We could look at the
184   // getter implementation to find the ivar when its name is not derived from
185   // the property name.
186   return 0;
187 }
188 
189 void IvarInvalidationChecker::checkASTDecl(const ObjCMethodDecl *D,
190                                           AnalysisManager& Mgr,
191                                           BugReporter &BR) const {
192   // We are only interested in checking the cleanup methods.
193   if (!D->hasBody() || !isInvalidationMethod(D))
194     return;
195 
196   // Collect all ivars that need cleanup.
197   IvarSet Ivars;
198   const ObjCInterfaceDecl *InterfaceD = D->getClassInterface();
199   for (ObjCInterfaceDecl::ivar_iterator
200       II = InterfaceD->ivar_begin(),
201       IE = InterfaceD->ivar_end(); II != IE; ++II) {
202     const ObjCIvarDecl *Iv = *II;
203     QualType IvQTy = Iv->getType();
204     const ObjCObjectPointerType *IvTy = IvQTy->getAs<ObjCObjectPointerType>();
205     if (!IvTy)
206       continue;
207     const ObjCInterfaceDecl *IvInterf = IvTy->getObjectType()->getInterface();
208     if (containsInvalidationMethod(IvInterf))
209       Ivars[cast<ObjCIvarDecl>(Iv->getCanonicalDecl())] = false;
210   }
211 
212   // Construct Property/Property Setter to Ivar maps to assist checking if an
213   // ivar which is backing a property has been reset.
214   MethToIvarMapTy PropSetterToIvarMap;
215   PropToIvarMapTy PropertyToIvarMap;
216   for (ObjCInterfaceDecl::prop_iterator
217       I = InterfaceD->prop_begin(),
218       E = InterfaceD->prop_end(); I != E; ++I) {
219     const ObjCPropertyDecl *PD = *I;
220 
221     const ObjCIvarDecl *ID = findPropertyBackingIvar(PD, InterfaceD, Ivars);
222     if (!ID) {
223       continue;
224     }
225     // Find the setter.
226     const ObjCMethodDecl *SetterD = PD->getSetterMethodDecl();
227     // If we don't know the setter, do not track this ivar.
228     if (!SetterD) {
229       Ivars[cast<ObjCIvarDecl>(ID->getCanonicalDecl())] = true;
230       continue;
231     }
232 
233     // Store the mappings.
234     PD = cast<ObjCPropertyDecl>(PD->getCanonicalDecl());
235     SetterD = cast<ObjCMethodDecl>(SetterD->getCanonicalDecl());
236     PropertyToIvarMap[PD] = ID;
237     PropSetterToIvarMap[SetterD] = ID;
238   }
239 
240 
241   // Check which ivars have been accessed by the method.
242   // We assume that if ivar was at least accessed, it was not forgotten.
243   MethodCrawler(InterfaceD, Ivars,
244                 PropSetterToIvarMap, PropertyToIvarMap).VisitStmt(D->getBody());
245 
246   // Warn on the ivars that were not accessed by the method.
247   for (IvarSet::const_iterator I = Ivars.begin(), E = Ivars.end(); I != E; ++I){
248     if (I->second == false) {
249       const ObjCIvarDecl *IvarDecl = I->first;
250 
251       PathDiagnosticLocation IvarDecLocation =
252           PathDiagnosticLocation::createBegin(IvarDecl, BR.getSourceManager());
253 
254       SmallString<128> sbuf;
255       llvm::raw_svector_ostream os(sbuf);
256       os << "Ivar needs to be invalidated in the '" <<
257             D->getSelector().getAsString()<< "' method";
258 
259       BR.EmitBasicReport(IvarDecl,
260           "Incomplete invalidation",
261           categories::CoreFoundationObjectiveC, os.str(),
262           IvarDecLocation);
263     }
264   }
265 }
266 
267 /// Handle the case when an ivar is directly accessed.
268 void IvarInvalidationChecker::MethodCrawler::VisitObjCIvarRefExpr(
269     const ObjCIvarRefExpr *IvarRef) {
270   const Decl *D = IvarRef->getDecl();
271   if (D)
272     IVars[cast<ObjCIvarDecl>(D->getCanonicalDecl())] = true;
273   VisitStmt(IvarRef);
274 }
275 
276 
277 /// Handle the case when the property backing ivar is set via a direct call
278 /// to the setter.
279 void IvarInvalidationChecker::MethodCrawler::VisitObjCMessageExpr(
280     const ObjCMessageExpr *ME) {
281   const ObjCMethodDecl *MD = ME->getMethodDecl();
282   if (MD) {
283     MD = cast<ObjCMethodDecl>(MD->getCanonicalDecl());
284     IVars[PropertySetterToIvarMap[MD]] = true;
285   }
286   VisitStmt(ME);
287 }
288 
289 /// Handle the case when the property backing ivar is set via the dot syntax.
290 void IvarInvalidationChecker::MethodCrawler::VisitObjCPropertyRefExpr(
291     const ObjCPropertyRefExpr *PA) {
292 
293   if (PA->isExplicitProperty()) {
294     const ObjCPropertyDecl *PD = PA->getExplicitProperty();
295     if (PD) {
296       PD = cast<ObjCPropertyDecl>(PD->getCanonicalDecl());
297       IVars[PropertyToIvarMap[PD]] = true;
298       VisitStmt(PA);
299       return;
300     }
301   }
302 
303   if (PA->isImplicitProperty()) {
304     const ObjCMethodDecl *MD = PA->getImplicitPropertySetter();
305     if (MD) {
306       MD = cast<ObjCMethodDecl>(MD->getCanonicalDecl());
307       IVars[PropertySetterToIvarMap[MD]] = true;
308       VisitStmt(PA);
309       return;
310     }
311   }
312   VisitStmt(PA);
313 }
314 }
315 
316 // Register the checker.
317 void ento::registerIvarInvalidationChecker(CheckerManager &mgr) {
318   mgr.registerChecker<IvarInvalidationChecker>();
319 }
320