DirectIvarAssignment.cpp revision d7b1d2467d8bf01be5068dbbad1a6324cee8bf4a
1//=- DirectIvarAssignment.cpp - Check rules on ObjC properties -*- 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//  Check that Objective C properties follow the following rules:
11//    - The property should be set with the setter, not though a direct
12//      assignment.
13//
14//===----------------------------------------------------------------------===//
15
16#include "ClangSACheckers.h"
17#include "clang/AST/Attr.h"
18#include "clang/AST/DeclObjC.h"
19#include "clang/AST/StmtVisitor.h"
20#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
21#include "clang/StaticAnalyzer/Core/Checker.h"
22#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
23#include "llvm/ADT/DenseMap.h"
24
25using namespace clang;
26using namespace ento;
27
28namespace {
29
30/// The default method filter, which is used to filter out the methods on which
31/// the check should not be performed.
32///
33/// Checks for the init, dealloc, and any other functions that might be allowed
34/// to perform direct instance variable assignment based on their name.
35struct MethodFilter {
36  virtual ~MethodFilter() {}
37  virtual bool operator()(ObjCMethodDecl *M) {
38    if (M->getMethodFamily() == OMF_init ||
39        M->getMethodFamily() == OMF_dealloc ||
40        M->getMethodFamily() == OMF_copy ||
41        M->getMethodFamily() == OMF_mutableCopy ||
42        M->getSelector().getNameForSlot(0).find("init") != StringRef::npos ||
43        M->getSelector().getNameForSlot(0).find("Init") != StringRef::npos)
44      return true;
45    return false;
46  }
47};
48
49static MethodFilter DefaultMethodFilter;
50
51class DirectIvarAssignment :
52  public Checker<check::ASTDecl<ObjCImplementationDecl> > {
53
54  typedef llvm::DenseMap<const ObjCIvarDecl*,
55                         const ObjCPropertyDecl*> IvarToPropertyMapTy;
56
57  /// A helper class, which walks the AST and locates all assignments to ivars
58  /// in the given function.
59  class MethodCrawler : public ConstStmtVisitor<MethodCrawler> {
60    const IvarToPropertyMapTy &IvarToPropMap;
61    const ObjCMethodDecl *MD;
62    const ObjCInterfaceDecl *InterfD;
63    BugReporter &BR;
64    LocationOrAnalysisDeclContext DCtx;
65
66  public:
67    MethodCrawler(const IvarToPropertyMapTy &InMap, const ObjCMethodDecl *InMD,
68        const ObjCInterfaceDecl *InID,
69        BugReporter &InBR, AnalysisDeclContext *InDCtx)
70    : IvarToPropMap(InMap), MD(InMD), InterfD(InID), BR(InBR), DCtx(InDCtx) {}
71
72    void VisitStmt(const Stmt *S) { VisitChildren(S); }
73
74    void VisitBinaryOperator(const BinaryOperator *BO);
75
76    void VisitChildren(const Stmt *S) {
77      for (Stmt::const_child_range I = S->children(); I; ++I)
78        if (*I)
79         this->Visit(*I);
80    }
81  };
82
83public:
84  MethodFilter *ShouldSkipMethod;
85
86  DirectIvarAssignment() : ShouldSkipMethod(&DefaultMethodFilter) {}
87
88  void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager& Mgr,
89                    BugReporter &BR) const;
90};
91
92static const ObjCIvarDecl *findPropertyBackingIvar(const ObjCPropertyDecl *PD,
93                                               const ObjCInterfaceDecl *InterD,
94                                               ASTContext &Ctx) {
95  // Check for synthesized ivars.
96  ObjCIvarDecl *ID = PD->getPropertyIvarDecl();
97  if (ID)
98    return ID;
99
100  ObjCInterfaceDecl *NonConstInterD = const_cast<ObjCInterfaceDecl*>(InterD);
101
102  // Check for existing "_PropName".
103  ID = NonConstInterD->lookupInstanceVariable(PD->getDefaultSynthIvarName(Ctx));
104  if (ID)
105    return ID;
106
107  // Check for existing "PropName".
108  IdentifierInfo *PropIdent = PD->getIdentifier();
109  ID = NonConstInterD->lookupInstanceVariable(PropIdent);
110
111  return ID;
112}
113
114void DirectIvarAssignment::checkASTDecl(const ObjCImplementationDecl *D,
115                                       AnalysisManager& Mgr,
116                                       BugReporter &BR) const {
117  const ObjCInterfaceDecl *InterD = D->getClassInterface();
118
119
120  IvarToPropertyMapTy IvarToPropMap;
121
122  // Find all properties for this class.
123  for (ObjCInterfaceDecl::prop_iterator I = InterD->prop_begin(),
124      E = InterD->prop_end(); I != E; ++I) {
125    ObjCPropertyDecl *PD = *I;
126
127    // Find the corresponding IVar.
128    const ObjCIvarDecl *ID = findPropertyBackingIvar(PD, InterD,
129                                                     Mgr.getASTContext());
130
131    if (!ID)
132      continue;
133
134    // Store the IVar to property mapping.
135    IvarToPropMap[ID] = PD;
136  }
137
138  if (IvarToPropMap.empty())
139    return;
140
141  for (ObjCImplementationDecl::instmeth_iterator I = D->instmeth_begin(),
142      E = D->instmeth_end(); I != E; ++I) {
143
144    ObjCMethodDecl *M = *I;
145    AnalysisDeclContext *DCtx = Mgr.getAnalysisDeclContext(M);
146
147    if ((*ShouldSkipMethod)(M))
148      continue;
149
150    const Stmt *Body = M->getBody();
151    assert(Body);
152
153    MethodCrawler MC(IvarToPropMap, M->getCanonicalDecl(), InterD, BR, DCtx);
154    MC.VisitStmt(Body);
155  }
156}
157
158static bool isAnnotatedToAllowDirectAssignment(const ObjCPropertyDecl *D) {
159  for (specific_attr_iterator<AnnotateAttr>
160       AI = D->specific_attr_begin<AnnotateAttr>(),
161       AE = D->specific_attr_end<AnnotateAttr>(); AI != AE; ++AI) {
162    const AnnotateAttr *Ann = *AI;
163    if (Ann->getAnnotation() ==
164        "objc_allow_direct_instance_variable_assignment")
165      return true;
166  }
167  return false;
168}
169
170void DirectIvarAssignment::MethodCrawler::VisitBinaryOperator(
171                                                    const BinaryOperator *BO) {
172  if (!BO->isAssignmentOp())
173    return;
174
175  const ObjCIvarRefExpr *IvarRef =
176          dyn_cast<ObjCIvarRefExpr>(BO->getLHS()->IgnoreParenCasts());
177
178  if (!IvarRef)
179    return;
180
181  if (const ObjCIvarDecl *D = IvarRef->getDecl()) {
182    IvarToPropertyMapTy::const_iterator I = IvarToPropMap.find(D);
183
184    if (I != IvarToPropMap.end()) {
185      const ObjCPropertyDecl *PD = I->second;
186      // Skip warnings on Ivars that correspond to properties, annotated with
187      // objc_allow_direct_instance_variable_assignment. This annotation serves
188      // as a false positive suppression mechanism for the checker.
189      if (isAnnotatedToAllowDirectAssignment(PD))
190        return;
191
192      ObjCMethodDecl *GetterMethod =
193          InterfD->getInstanceMethod(PD->getGetterName());
194      ObjCMethodDecl *SetterMethod =
195          InterfD->getInstanceMethod(PD->getSetterName());
196
197      if (SetterMethod && SetterMethod->getCanonicalDecl() == MD)
198        return;
199
200      if (GetterMethod && GetterMethod->getCanonicalDecl() == MD)
201        return;
202
203      BR.EmitBasicReport(MD,
204          "Property access",
205          categories::CoreFoundationObjectiveC,
206          "Direct assignment to an instance variable backing a property; "
207          "use the setter instead", PathDiagnosticLocation(IvarRef,
208                                                          BR.getSourceManager(),
209                                                          DCtx));
210    }
211  }
212}
213}
214
215// Register the checker that checks for direct accesses in all functions,
216// except for the initialization and copy routines.
217void ento::registerDirectIvarAssignment(CheckerManager &mgr) {
218  mgr.registerChecker<DirectIvarAssignment>();
219}
220
221// Register the checker that checks for direct accesses in functions annotated
222// with __attribute__((annotate("objc_no_direct_instance_variable_assignment"))).
223namespace {
224struct InvalidatorMethodFilter : MethodFilter {
225  virtual ~InvalidatorMethodFilter() {}
226  virtual bool operator()(ObjCMethodDecl *M) {
227    for (specific_attr_iterator<AnnotateAttr>
228         AI = M->specific_attr_begin<AnnotateAttr>(),
229         AE = M->specific_attr_end<AnnotateAttr>(); AI != AE; ++AI) {
230      const AnnotateAttr *Ann = *AI;
231      if (Ann->getAnnotation() == "objc_no_direct_instance_variable_assignment")
232        return false;
233    }
234    return true;
235  }
236};
237
238InvalidatorMethodFilter AttrFilter;
239}
240
241void ento::registerDirectIvarAssignmentForAnnotatedFunctions(
242    CheckerManager &mgr) {
243  mgr.registerChecker<DirectIvarAssignment>()->ShouldSkipMethod = &AttrFilter;
244}
245