IvarInvalidationChecker.cpp revision 5bf5c2ec54ede5352293e5739e9b44bea2f6b01b
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    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}
320