DirectIvarAssignment.cpp revision aacadfea7a7174116dbde09937098763a3211396
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
158void DirectIvarAssignment::MethodCrawler::VisitBinaryOperator(
159                                                    const BinaryOperator *BO) {
160  if (!BO->isAssignmentOp())
161    return;
162
163  const ObjCIvarRefExpr *IvarRef =
164          dyn_cast<ObjCIvarRefExpr>(BO->getLHS()->IgnoreParenCasts());
165
166  if (!IvarRef)
167    return;
168
169  if (const ObjCIvarDecl *D = IvarRef->getDecl()) {
170    IvarToPropertyMapTy::const_iterator I = IvarToPropMap.find(D);
171    if (I != IvarToPropMap.end()) {
172      const ObjCPropertyDecl *PD = I->second;
173
174      ObjCMethodDecl *GetterMethod =
175          InterfD->getInstanceMethod(PD->getGetterName());
176      ObjCMethodDecl *SetterMethod =
177          InterfD->getInstanceMethod(PD->getSetterName());
178
179      if (SetterMethod && SetterMethod->getCanonicalDecl() == MD)
180        return;
181
182      if (GetterMethod && GetterMethod->getCanonicalDecl() == MD)
183        return;
184
185      BR.EmitBasicReport(MD,
186          "Property access",
187          categories::CoreFoundationObjectiveC,
188          "Direct assignment to an instance variable backing a property; "
189          "use the setter instead", PathDiagnosticLocation(IvarRef,
190                                                          BR.getSourceManager(),
191                                                          DCtx));
192    }
193  }
194}
195}
196
197// Register the checker that checks for direct accesses in all functions,
198// except for the initialization and copy routines.
199void ento::registerDirectIvarAssignment(CheckerManager &mgr) {
200  mgr.registerChecker<DirectIvarAssignment>();
201}
202
203// Register the checker that checks for direct accesses in functions annotated
204// with __attribute__((annotate("objc_no_direct_instance_variable_assignmemt"))).
205namespace {
206struct InvalidatorMethodFilter : MethodFilter {
207  virtual ~InvalidatorMethodFilter() {}
208  virtual bool operator()(ObjCMethodDecl *M) {
209    for (specific_attr_iterator<AnnotateAttr>
210         AI = M->specific_attr_begin<AnnotateAttr>(),
211         AE = M->specific_attr_end<AnnotateAttr>(); AI != AE; ++AI) {
212      const AnnotateAttr *Ann = *AI;
213      if (Ann->getAnnotation() == "objc_no_direct_instance_variable_assignmemt")
214        return false;
215    }
216    return true;
217  }
218};
219
220InvalidatorMethodFilter AttrFilter;
221}
222
223void ento::registerDirectIvarAssignmentForAnnotatedFunctions(
224    CheckerManager &mgr) {
225  mgr.registerChecker<DirectIvarAssignment>()->ShouldSkipMethod = &AttrFilter;
226}
227