IvarInvalidationChecker.cpp revision 377945cc9e4f23cdbb01ade2a664acd5ff95a888
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
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  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
100public:
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
107bool 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
118bool 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
162bool 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
176const 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
218void 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.
307void 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.
318void 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.
329void 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.
356void ento::registerIvarInvalidationChecker(CheckerManager &mgr) {
357  mgr.registerChecker<IvarInvalidationChecker>();
358}
359