IvarInvalidationChecker.cpp revision 51431dcf4a591ded089a56aaa985bb546cec8ce4
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. There could be multiple
17//  methods annotated with such annotations per class, either one can be used
18//  to invalidate the ivar. An ivar or property are considered to be
19//  invalidated if they are being assigned 'nil' or an invalidation method has
20//  been called on them. An invalidation method should either invalidate all
21//  the ivars or call another invalidation method (on self).
22//
23//===----------------------------------------------------------------------===//
24
25#include "ClangSACheckers.h"
26#include "clang/StaticAnalyzer/Core/Checker.h"
27#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
28#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
29#include "clang/AST/DeclObjC.h"
30#include "clang/AST/StmtVisitor.h"
31#include "llvm/ADT/DenseMap.h"
32#include "llvm/ADT/SmallString.h"
33
34using namespace clang;
35using namespace ento;
36
37namespace {
38class IvarInvalidationChecker :
39  public Checker<check::ASTDecl<ObjCMethodDecl> > {
40
41  typedef llvm::DenseSet<const ObjCMethodDecl*> MethodSet;
42  typedef llvm::DenseMap<const ObjCMethodDecl*,
43                         const ObjCIvarDecl*> MethToIvarMapTy;
44  typedef llvm::DenseMap<const ObjCPropertyDecl*,
45                         const ObjCIvarDecl*> PropToIvarMapTy;
46  typedef llvm::DenseMap<const ObjCIvarDecl*,
47                         const ObjCPropertyDecl*> IvarToPropMapTy;
48
49
50  struct IvarInfo {
51    /// Has the ivar been invalidated?
52    bool IsInvalidated;
53
54    /// The methods which can be used to invalidate the ivar.
55    MethodSet InvalidationMethods;
56
57    IvarInfo() : IsInvalidated(false) {}
58    void addInvalidationMethod(const ObjCMethodDecl *MD) {
59      InvalidationMethods.insert(MD);
60    }
61
62    bool needsInvalidation() const {
63      return !InvalidationMethods.empty();
64    }
65
66    void markInvalidated() {
67      IsInvalidated = true;
68    }
69
70    bool markInvalidated(const ObjCMethodDecl *MD) {
71      if (IsInvalidated)
72        return true;
73      for (MethodSet::iterator I = InvalidationMethods.begin(),
74          E = InvalidationMethods.end(); I != E; ++I) {
75        if (*I == MD) {
76          IsInvalidated = true;
77          return true;
78        }
79      }
80      return false;
81    }
82
83    bool isInvalidated() const {
84      return IsInvalidated;
85    }
86  };
87
88  typedef llvm::DenseMap<const ObjCIvarDecl*, IvarInfo> IvarSet;
89
90  /// Statement visitor, which walks the method body and flags the ivars
91  /// referenced in it (either directly or via property).
92  class MethodCrawler : public ConstStmtVisitor<MethodCrawler> {
93    /// The set of Ivars which need to be invalidated.
94    IvarSet &IVars;
95
96    /// Flag is set as the result of a message send to another
97    /// invalidation method.
98    bool &CalledAnotherInvalidationMethod;
99
100    /// Property setter to ivar mapping.
101    const MethToIvarMapTy &PropertySetterToIvarMap;
102
103    /// Property getter to ivar mapping.
104    const MethToIvarMapTy &PropertyGetterToIvarMap;
105
106    /// Property to ivar mapping.
107    const PropToIvarMapTy &PropertyToIvarMap;
108
109    /// The invalidation method being currently processed.
110    const ObjCMethodDecl *InvalidationMethod;
111
112    ASTContext &Ctx;
113
114    /// Peel off parens, casts, OpaqueValueExpr, and PseudoObjectExpr.
115    const Expr *peel(const Expr *E) const;
116
117    /// Does this expression represent zero: '0'?
118    bool isZero(const Expr *E) const;
119
120    /// Mark the given ivar as invalidated.
121    void markInvalidated(const ObjCIvarDecl *Iv);
122
123    /// Checks if IvarRef refers to the tracked IVar, if yes, marks it as
124    /// invalidated.
125    void checkObjCIvarRefExpr(const ObjCIvarRefExpr *IvarRef);
126
127    /// Checks if ObjCPropertyRefExpr refers to the tracked IVar, if yes, marks
128    /// it as invalidated.
129    void checkObjCPropertyRefExpr(const ObjCPropertyRefExpr *PA);
130
131    /// Checks if ObjCMessageExpr refers to (is a getter for) the tracked IVar,
132    /// if yes, marks it as invalidated.
133    void checkObjCMessageExpr(const ObjCMessageExpr *ME);
134
135    /// Checks if the Expr refers to an ivar, if yes, marks it as invalidated.
136    void check(const Expr *E);
137
138  public:
139    MethodCrawler(IvarSet &InIVars,
140                  bool &InCalledAnotherInvalidationMethod,
141                  const MethToIvarMapTy &InPropertySetterToIvarMap,
142                  const MethToIvarMapTy &InPropertyGetterToIvarMap,
143                  const PropToIvarMapTy &InPropertyToIvarMap,
144                  ASTContext &InCtx)
145    : IVars(InIVars),
146      CalledAnotherInvalidationMethod(InCalledAnotherInvalidationMethod),
147      PropertySetterToIvarMap(InPropertySetterToIvarMap),
148      PropertyGetterToIvarMap(InPropertyGetterToIvarMap),
149      PropertyToIvarMap(InPropertyToIvarMap),
150      InvalidationMethod(0),
151      Ctx(InCtx) {}
152
153    void VisitStmt(const Stmt *S) { VisitChildren(S); }
154
155    void VisitBinaryOperator(const BinaryOperator *BO);
156
157    void VisitObjCMessageExpr(const ObjCMessageExpr *ME);
158
159    void VisitChildren(const Stmt *S) {
160      for (Stmt::const_child_range I = S->children(); I; ++I) {
161        if (*I)
162          this->Visit(*I);
163        if (CalledAnotherInvalidationMethod)
164          return;
165      }
166    }
167  };
168
169  /// Check if the any of the methods inside the interface are annotated with
170  /// the invalidation annotation, update the IvarInfo accordingly.
171  static void containsInvalidationMethod(const ObjCContainerDecl *D,
172                                         IvarInfo &Out);
173
174  /// Check if ivar should be tracked and add to TrackedIvars if positive.
175  /// Returns true if ivar should be tracked.
176  static bool trackIvar(const ObjCIvarDecl *Iv, IvarSet &TrackedIvars);
177
178  /// Given the property declaration, and the list of tracked ivars, finds
179  /// the ivar backing the property when possible. Returns '0' when no such
180  /// ivar could be found.
181  static const ObjCIvarDecl *findPropertyBackingIvar(
182      const ObjCPropertyDecl *Prop,
183      const ObjCInterfaceDecl *InterfaceD,
184      IvarSet &TrackedIvars);
185
186public:
187  void checkASTDecl(const ObjCMethodDecl *D, AnalysisManager& Mgr,
188                    BugReporter &BR) const;
189
190  // TODO: We are currently ignoring the ivars coming from class extensions.
191};
192
193static bool isInvalidationMethod(const ObjCMethodDecl *M) {
194  for (specific_attr_iterator<AnnotateAttr>
195       AI = M->specific_attr_begin<AnnotateAttr>(),
196       AE = M->specific_attr_end<AnnotateAttr>(); AI != AE; ++AI) {
197    const AnnotateAttr *Ann = *AI;
198    if (Ann->getAnnotation() == "objc_instance_variable_invalidator")
199      return true;
200  }
201  return false;
202}
203
204void IvarInvalidationChecker::containsInvalidationMethod(
205    const ObjCContainerDecl *D, IvarInfo &OutInfo) {
206
207  // TODO: Cache the results.
208
209  if (!D)
210    return;
211
212  // Check all methods.
213  for (ObjCContainerDecl::method_iterator
214      I = D->meth_begin(),
215      E = D->meth_end(); I != E; ++I) {
216      const ObjCMethodDecl *MDI = *I;
217      if (isInvalidationMethod(MDI))
218        OutInfo.addInvalidationMethod(
219                               cast<ObjCMethodDecl>(MDI->getCanonicalDecl()));
220  }
221
222  // If interface, check all parent protocols and super.
223  // TODO: Visit all categories in case the invalidation method is declared in
224  // a category.
225  if (const ObjCInterfaceDecl *InterfaceD = dyn_cast<ObjCInterfaceDecl>(D)) {
226    for (ObjCInterfaceDecl::protocol_iterator
227        I = InterfaceD->protocol_begin(),
228        E = InterfaceD->protocol_end(); I != E; ++I) {
229      containsInvalidationMethod(*I, OutInfo);
230    }
231    containsInvalidationMethod(InterfaceD->getSuperClass(), OutInfo);
232    return;
233  }
234
235  // If protocol, check all parent protocols.
236  if (const ObjCProtocolDecl *ProtD = dyn_cast<ObjCProtocolDecl>(D)) {
237    for (ObjCInterfaceDecl::protocol_iterator
238        I = ProtD->protocol_begin(),
239        E = ProtD->protocol_end(); I != E; ++I) {
240      containsInvalidationMethod(*I, OutInfo);
241    }
242    return;
243  }
244
245  llvm_unreachable("One of the casts above should have succeeded.");
246}
247
248bool IvarInvalidationChecker::trackIvar(const ObjCIvarDecl *Iv,
249                                        IvarSet &TrackedIvars) {
250  QualType IvQTy = Iv->getType();
251  const ObjCObjectPointerType *IvTy = IvQTy->getAs<ObjCObjectPointerType>();
252  if (!IvTy)
253    return false;
254  const ObjCInterfaceDecl *IvInterf = IvTy->getInterfaceDecl();
255
256  IvarInfo Info;
257  containsInvalidationMethod(IvInterf, Info);
258  if (Info.needsInvalidation()) {
259    TrackedIvars[cast<ObjCIvarDecl>(Iv->getCanonicalDecl())] = Info;
260    return true;
261  }
262  return false;
263}
264
265const ObjCIvarDecl *IvarInvalidationChecker::findPropertyBackingIvar(
266                        const ObjCPropertyDecl *Prop,
267                        const ObjCInterfaceDecl *InterfaceD,
268                        IvarSet &TrackedIvars) {
269  const ObjCIvarDecl *IvarD = 0;
270
271  // Lookup for the synthesized case.
272  IvarD = Prop->getPropertyIvarDecl();
273  if (IvarD) {
274    if (TrackedIvars.count(IvarD)) {
275      return IvarD;
276    }
277    // If the ivar is synthesized we still want to track it.
278    if (trackIvar(IvarD, TrackedIvars))
279      return IvarD;
280  }
281
282  // Lookup IVars named "_PropName"or "PropName" among the tracked Ivars.
283  StringRef PropName = Prop->getIdentifier()->getName();
284  for (IvarSet::const_iterator I = TrackedIvars.begin(),
285                               E = TrackedIvars.end(); I != E; ++I) {
286    const ObjCIvarDecl *Iv = I->first;
287    StringRef IvarName = Iv->getName();
288
289    if (IvarName == PropName)
290      return Iv;
291
292    SmallString<128> PropNameWithUnderscore;
293    {
294      llvm::raw_svector_ostream os(PropNameWithUnderscore);
295      os << '_' << PropName;
296    }
297    if (IvarName == PropNameWithUnderscore.str())
298      return Iv;
299  }
300
301  // Note, this is a possible source of false positives. We could look at the
302  // getter implementation to find the ivar when its name is not derived from
303  // the property name.
304  return 0;
305}
306
307void IvarInvalidationChecker::checkASTDecl(const ObjCMethodDecl *D,
308                                          AnalysisManager& Mgr,
309                                          BugReporter &BR) const {
310  // We are only interested in checking the cleanup methods.
311  if (!D->hasBody() || !isInvalidationMethod(D))
312    return;
313
314  // Collect all ivars that need cleanup.
315  IvarSet Ivars;
316  const ObjCInterfaceDecl *InterfaceD = D->getClassInterface();
317  for (ObjCInterfaceDecl::ivar_iterator
318      II = InterfaceD->ivar_begin(),
319      IE = InterfaceD->ivar_end(); II != IE; ++II) {
320    const ObjCIvarDecl *Iv = *II;
321    trackIvar(Iv, Ivars);
322  }
323
324  // Construct Property/Property Accessor to Ivar maps to assist checking if an
325  // ivar which is backing a property has been reset.
326  MethToIvarMapTy PropSetterToIvarMap;
327  MethToIvarMapTy PropGetterToIvarMap;
328  PropToIvarMapTy PropertyToIvarMap;
329  IvarToPropMapTy IvarToPopertyMap;
330  for (ObjCInterfaceDecl::prop_iterator
331      I = InterfaceD->prop_begin(),
332      E = InterfaceD->prop_end(); I != E; ++I) {
333    const ObjCPropertyDecl *PD = *I;
334
335    const ObjCIvarDecl *ID = findPropertyBackingIvar(PD, InterfaceD, Ivars);
336    if (!ID) {
337      continue;
338    }
339
340    // Store the mappings.
341    PD = cast<ObjCPropertyDecl>(PD->getCanonicalDecl());
342    PropertyToIvarMap[PD] = ID;
343    IvarToPopertyMap[ID] = PD;
344
345    // Find the setter and the getter.
346    const ObjCMethodDecl *SetterD = PD->getSetterMethodDecl();
347    if (SetterD) {
348      SetterD = cast<ObjCMethodDecl>(SetterD->getCanonicalDecl());
349      PropSetterToIvarMap[SetterD] = ID;
350    }
351
352    const ObjCMethodDecl *GetterD = PD->getGetterMethodDecl();
353    if (GetterD) {
354      GetterD = cast<ObjCMethodDecl>(GetterD->getCanonicalDecl());
355      PropGetterToIvarMap[GetterD] = ID;
356    }
357  }
358
359
360  // Check which ivars have been invalidated in the method body.
361  bool CalledAnotherInvalidationMethod = false;
362  MethodCrawler(Ivars,
363                CalledAnotherInvalidationMethod,
364                PropSetterToIvarMap,
365                PropGetterToIvarMap,
366                PropertyToIvarMap,
367                BR.getContext()).VisitStmt(D->getBody());
368
369  if (CalledAnotherInvalidationMethod)
370    return;
371
372  // Warn on the ivars that were not accessed by the method.
373  for (IvarSet::const_iterator I = Ivars.begin(), E = Ivars.end(); I != E; ++I){
374    if (!I->second.isInvalidated()) {
375      const ObjCIvarDecl *IvarDecl = I->first;
376
377      PathDiagnosticLocation IvarDecLocation =
378          PathDiagnosticLocation::createEnd(D->getBody(), BR.getSourceManager(),
379                                            Mgr.getAnalysisDeclContext(D));
380
381      SmallString<128> sbuf;
382      llvm::raw_svector_ostream os(sbuf);
383
384      // Construct the warning message.
385      if (IvarDecl->getSynthesize()) {
386        const ObjCPropertyDecl *PD = IvarToPopertyMap[IvarDecl];
387        assert(PD &&
388               "Do we synthesize ivars for something other than properties?");
389        os << "Property "<< PD->getName() << " needs to be invalidated";
390      } else {
391        os << "Instance variable "<< IvarDecl->getName()
392             << " needs to be invalidated or set to nil";
393      }
394
395      BR.EmitBasicReport(D,
396          "Incomplete invalidation",
397          categories::CoreFoundationObjectiveC, os.str(),
398          IvarDecLocation);
399    }
400  }
401}
402
403void IvarInvalidationChecker::MethodCrawler::markInvalidated(
404    const ObjCIvarDecl *Iv) {
405  IvarSet::iterator I = IVars.find(Iv);
406  if (I != IVars.end()) {
407    // If InvalidationMethod is present, we are processing the message send and
408    // should ensure we are invalidating with the appropriate method,
409    // otherwise, we are processing setting to 'nil'.
410    if (InvalidationMethod)
411      I->second.markInvalidated(InvalidationMethod);
412    else
413      I->second.markInvalidated();
414  }
415}
416
417const Expr *IvarInvalidationChecker::MethodCrawler::peel(const Expr *E) const {
418  E = E->IgnoreParenCasts();
419  if (const PseudoObjectExpr *POE = dyn_cast<PseudoObjectExpr>(E))
420    E = POE->getSyntacticForm()->IgnoreParenCasts();
421  if (const OpaqueValueExpr *OVE = dyn_cast<OpaqueValueExpr>(E))
422    E = OVE->getSourceExpr()->IgnoreParenCasts();
423  return E;
424}
425
426void IvarInvalidationChecker::MethodCrawler::checkObjCIvarRefExpr(
427    const ObjCIvarRefExpr *IvarRef) {
428  if (const Decl *D = IvarRef->getDecl())
429    markInvalidated(cast<ObjCIvarDecl>(D->getCanonicalDecl()));
430}
431
432void IvarInvalidationChecker::MethodCrawler::checkObjCMessageExpr(
433    const ObjCMessageExpr *ME) {
434  const ObjCMethodDecl *MD = ME->getMethodDecl();
435  if (MD) {
436    MD = cast<ObjCMethodDecl>(MD->getCanonicalDecl());
437    MethToIvarMapTy::const_iterator IvI = PropertyGetterToIvarMap.find(MD);
438    if (IvI != PropertyGetterToIvarMap.end())
439      markInvalidated(IvI->second);
440  }
441}
442
443void IvarInvalidationChecker::MethodCrawler::checkObjCPropertyRefExpr(
444    const ObjCPropertyRefExpr *PA) {
445
446  if (PA->isExplicitProperty()) {
447    const ObjCPropertyDecl *PD = PA->getExplicitProperty();
448    if (PD) {
449      PD = cast<ObjCPropertyDecl>(PD->getCanonicalDecl());
450      PropToIvarMapTy::const_iterator IvI = PropertyToIvarMap.find(PD);
451      if (IvI != PropertyToIvarMap.end())
452        markInvalidated(IvI->second);
453      return;
454    }
455  }
456
457  if (PA->isImplicitProperty()) {
458    const ObjCMethodDecl *MD = PA->getImplicitPropertySetter();
459    if (MD) {
460      MD = cast<ObjCMethodDecl>(MD->getCanonicalDecl());
461      MethToIvarMapTy::const_iterator IvI =PropertyGetterToIvarMap.find(MD);
462      if (IvI != PropertyGetterToIvarMap.end())
463        markInvalidated(IvI->second);
464      return;
465    }
466  }
467}
468
469bool IvarInvalidationChecker::MethodCrawler::isZero(const Expr *E) const {
470  E = peel(E);
471
472  return (E->isNullPointerConstant(Ctx, Expr::NPC_ValueDependentIsNotNull)
473           != Expr::NPCK_NotNull);
474}
475
476void IvarInvalidationChecker::MethodCrawler::check(const Expr *E) {
477  E = peel(E);
478
479  if (const ObjCIvarRefExpr *IvarRef = dyn_cast<ObjCIvarRefExpr>(E)) {
480    checkObjCIvarRefExpr(IvarRef);
481    return;
482  }
483
484  if (const ObjCPropertyRefExpr *PropRef = dyn_cast<ObjCPropertyRefExpr>(E)) {
485    checkObjCPropertyRefExpr(PropRef);
486    return;
487  }
488
489  if (const ObjCMessageExpr *MsgExpr = dyn_cast<ObjCMessageExpr>(E)) {
490    checkObjCMessageExpr(MsgExpr);
491    return;
492  }
493}
494
495void IvarInvalidationChecker::MethodCrawler::VisitBinaryOperator(
496    const BinaryOperator *BO) {
497  VisitStmt(BO);
498
499  if (BO->getOpcode() != BO_Assign)
500    return;
501
502  // Do we assign zero?
503  if (!isZero(BO->getRHS()))
504    return;
505
506  // Check the variable we are assigning to.
507  check(BO->getLHS());
508}
509
510void IvarInvalidationChecker::MethodCrawler::VisitObjCMessageExpr(
511    const ObjCMessageExpr *ME) {
512  const ObjCMethodDecl *MD = ME->getMethodDecl();
513  const Expr *Receiver = ME->getInstanceReceiver();
514
515  // Stop if we are calling '[self invalidate]'.
516  if (Receiver && isInvalidationMethod(MD))
517    if (Receiver->isObjCSelfExpr()) {
518      CalledAnotherInvalidationMethod = true;
519      return;
520    }
521
522  // Check if we call a setter and set the property to 'nil'.
523  if (MD && (ME->getNumArgs() == 1) && isZero(ME->getArg(0))) {
524    MD = cast<ObjCMethodDecl>(MD->getCanonicalDecl());
525    MethToIvarMapTy::const_iterator IvI = PropertySetterToIvarMap.find(MD);
526    if (IvI != PropertySetterToIvarMap.end()) {
527      markInvalidated(IvI->second);
528      return;
529    }
530  }
531
532  // Check if we call the 'invalidation' routine on the ivar.
533  if (Receiver) {
534    InvalidationMethod = MD;
535    check(Receiver->IgnoreParenCasts());
536    InvalidationMethod = 0;
537  }
538
539  VisitStmt(ME);
540}
541}
542
543// Register the checker.
544void ento::registerIvarInvalidationChecker(CheckerManager &mgr) {
545  mgr.registerChecker<IvarInvalidationChecker>();
546}
547