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