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