IvarInvalidationChecker.cpp revision b087bbf3cf44a56d60ad1ed6fd5abb48dab0e0b3
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
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
93public:
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
100bool 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
111bool 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
155const 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
191void 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.
269void 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.
280void 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.
291void 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.
318void ento::registerIvarInvalidationChecker(CheckerManager &mgr) {
319  mgr.registerChecker<IvarInvalidationChecker>();
320}
321