blob: b08a2fbcb2f9cdbd1883475ca63eb8a897a37cae [file] [log] [blame]
Anna Zaks5bf5c2e2012-09-26 18:55:16 +00001//=- 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
32using namespace clang;
33using namespace ento;
34
35namespace {
36class 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
94public:
95 void checkASTDecl(const ObjCMethodDecl *D, AnalysisManager& Mgr,
96 BugReporter &BR) const;
97
98};
99
100bool 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
109bool 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
153const 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
189void 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.
268void 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.
279void 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.
290void 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.
317void ento::registerIvarInvalidationChecker(CheckerManager &mgr) {
318 mgr.registerChecker<IvarInvalidationChecker>();
319}