DirectIvarAssignment.cpp revision 249423
1243791Sdim//=- DirectIvarAssignment.cpp - Check rules on ObjC properties -*- C++ ----*-==//
2243791Sdim//
3243791Sdim//                     The LLVM Compiler Infrastructure
4243791Sdim//
5243791Sdim// This file is distributed under the University of Illinois Open Source
6243791Sdim// License. See LICENSE.TXT for details.
7243791Sdim//
8243791Sdim//===----------------------------------------------------------------------===//
9243791Sdim//
10249423Sdim//  Check that Objective C properties are set with the setter, not though a
11249423Sdim//      direct assignment.
12243791Sdim//
13249423Sdim//  Two versions of a checker exist: one that checks all methods and the other
14249423Sdim//      that only checks the methods annotated with
15249423Sdim//      __attribute__((annotate("objc_no_direct_instance_variable_assignment")))
16249423Sdim//
17249423Sdim//  The checker does not warn about assignments to Ivars, annotated with
18249423Sdim//       __attribute__((objc_allow_direct_instance_variable_assignment"))). This
19249423Sdim//      annotation serves as a false positive suppression mechanism for the
20249423Sdim//      checker. The annotation is allowed on properties and Ivars.
21249423Sdim//
22243791Sdim//===----------------------------------------------------------------------===//
23243791Sdim
24243791Sdim#include "ClangSACheckers.h"
25249423Sdim#include "clang/AST/Attr.h"
26249423Sdim#include "clang/AST/DeclObjC.h"
27249423Sdim#include "clang/AST/StmtVisitor.h"
28249423Sdim#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
29243791Sdim#include "clang/StaticAnalyzer/Core/Checker.h"
30243791Sdim#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
31243791Sdim#include "llvm/ADT/DenseMap.h"
32243791Sdim
33243791Sdimusing namespace clang;
34243791Sdimusing namespace ento;
35243791Sdim
36243791Sdimnamespace {
37243791Sdim
38249423Sdim/// The default method filter, which is used to filter out the methods on which
39249423Sdim/// the check should not be performed.
40249423Sdim///
41249423Sdim/// Checks for the init, dealloc, and any other functions that might be allowed
42249423Sdim/// to perform direct instance variable assignment based on their name.
43249423Sdimstruct MethodFilter {
44249423Sdim  virtual ~MethodFilter() {}
45249423Sdim  virtual bool operator()(ObjCMethodDecl *M) {
46249423Sdim    if (M->getMethodFamily() == OMF_init ||
47249423Sdim        M->getMethodFamily() == OMF_dealloc ||
48249423Sdim        M->getMethodFamily() == OMF_copy ||
49249423Sdim        M->getMethodFamily() == OMF_mutableCopy ||
50249423Sdim        M->getSelector().getNameForSlot(0).find("init") != StringRef::npos ||
51249423Sdim        M->getSelector().getNameForSlot(0).find("Init") != StringRef::npos)
52249423Sdim      return true;
53249423Sdim    return false;
54249423Sdim  }
55249423Sdim};
56249423Sdim
57249423Sdimstatic MethodFilter DefaultMethodFilter;
58249423Sdim
59243791Sdimclass DirectIvarAssignment :
60243791Sdim  public Checker<check::ASTDecl<ObjCImplementationDecl> > {
61243791Sdim
62243791Sdim  typedef llvm::DenseMap<const ObjCIvarDecl*,
63243791Sdim                         const ObjCPropertyDecl*> IvarToPropertyMapTy;
64243791Sdim
65243791Sdim  /// A helper class, which walks the AST and locates all assignments to ivars
66243791Sdim  /// in the given function.
67243791Sdim  class MethodCrawler : public ConstStmtVisitor<MethodCrawler> {
68243791Sdim    const IvarToPropertyMapTy &IvarToPropMap;
69243791Sdim    const ObjCMethodDecl *MD;
70243791Sdim    const ObjCInterfaceDecl *InterfD;
71243791Sdim    BugReporter &BR;
72243791Sdim    LocationOrAnalysisDeclContext DCtx;
73243791Sdim
74243791Sdim  public:
75243791Sdim    MethodCrawler(const IvarToPropertyMapTy &InMap, const ObjCMethodDecl *InMD,
76243791Sdim        const ObjCInterfaceDecl *InID,
77243791Sdim        BugReporter &InBR, AnalysisDeclContext *InDCtx)
78243791Sdim    : IvarToPropMap(InMap), MD(InMD), InterfD(InID), BR(InBR), DCtx(InDCtx) {}
79243791Sdim
80243791Sdim    void VisitStmt(const Stmt *S) { VisitChildren(S); }
81243791Sdim
82243791Sdim    void VisitBinaryOperator(const BinaryOperator *BO);
83243791Sdim
84243791Sdim    void VisitChildren(const Stmt *S) {
85243791Sdim      for (Stmt::const_child_range I = S->children(); I; ++I)
86243791Sdim        if (*I)
87243791Sdim         this->Visit(*I);
88243791Sdim    }
89243791Sdim  };
90243791Sdim
91243791Sdimpublic:
92249423Sdim  MethodFilter *ShouldSkipMethod;
93249423Sdim
94249423Sdim  DirectIvarAssignment() : ShouldSkipMethod(&DefaultMethodFilter) {}
95249423Sdim
96243791Sdim  void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager& Mgr,
97243791Sdim                    BugReporter &BR) const;
98243791Sdim};
99243791Sdim
100243791Sdimstatic const ObjCIvarDecl *findPropertyBackingIvar(const ObjCPropertyDecl *PD,
101243791Sdim                                               const ObjCInterfaceDecl *InterD,
102243791Sdim                                               ASTContext &Ctx) {
103243791Sdim  // Check for synthesized ivars.
104243791Sdim  ObjCIvarDecl *ID = PD->getPropertyIvarDecl();
105243791Sdim  if (ID)
106243791Sdim    return ID;
107243791Sdim
108243791Sdim  ObjCInterfaceDecl *NonConstInterD = const_cast<ObjCInterfaceDecl*>(InterD);
109243791Sdim
110243791Sdim  // Check for existing "_PropName".
111243791Sdim  ID = NonConstInterD->lookupInstanceVariable(PD->getDefaultSynthIvarName(Ctx));
112243791Sdim  if (ID)
113243791Sdim    return ID;
114243791Sdim
115243791Sdim  // Check for existing "PropName".
116243791Sdim  IdentifierInfo *PropIdent = PD->getIdentifier();
117243791Sdim  ID = NonConstInterD->lookupInstanceVariable(PropIdent);
118243791Sdim
119243791Sdim  return ID;
120243791Sdim}
121243791Sdim
122243791Sdimvoid DirectIvarAssignment::checkASTDecl(const ObjCImplementationDecl *D,
123243791Sdim                                       AnalysisManager& Mgr,
124243791Sdim                                       BugReporter &BR) const {
125243791Sdim  const ObjCInterfaceDecl *InterD = D->getClassInterface();
126243791Sdim
127243791Sdim
128243791Sdim  IvarToPropertyMapTy IvarToPropMap;
129243791Sdim
130243791Sdim  // Find all properties for this class.
131243791Sdim  for (ObjCInterfaceDecl::prop_iterator I = InterD->prop_begin(),
132243791Sdim      E = InterD->prop_end(); I != E; ++I) {
133243791Sdim    ObjCPropertyDecl *PD = *I;
134243791Sdim
135243791Sdim    // Find the corresponding IVar.
136243791Sdim    const ObjCIvarDecl *ID = findPropertyBackingIvar(PD, InterD,
137243791Sdim                                                     Mgr.getASTContext());
138243791Sdim
139243791Sdim    if (!ID)
140243791Sdim      continue;
141243791Sdim
142243791Sdim    // Store the IVar to property mapping.
143243791Sdim    IvarToPropMap[ID] = PD;
144243791Sdim  }
145243791Sdim
146243791Sdim  if (IvarToPropMap.empty())
147243791Sdim    return;
148243791Sdim
149243791Sdim  for (ObjCImplementationDecl::instmeth_iterator I = D->instmeth_begin(),
150243791Sdim      E = D->instmeth_end(); I != E; ++I) {
151243791Sdim
152243791Sdim    ObjCMethodDecl *M = *I;
153243791Sdim    AnalysisDeclContext *DCtx = Mgr.getAnalysisDeclContext(M);
154243791Sdim
155249423Sdim    if ((*ShouldSkipMethod)(M))
156243791Sdim      continue;
157243791Sdim
158243791Sdim    const Stmt *Body = M->getBody();
159243791Sdim    assert(Body);
160243791Sdim
161243791Sdim    MethodCrawler MC(IvarToPropMap, M->getCanonicalDecl(), InterD, BR, DCtx);
162243791Sdim    MC.VisitStmt(Body);
163243791Sdim  }
164243791Sdim}
165243791Sdim
166249423Sdimstatic bool isAnnotatedToAllowDirectAssignment(const Decl *D) {
167249423Sdim  for (specific_attr_iterator<AnnotateAttr>
168249423Sdim       AI = D->specific_attr_begin<AnnotateAttr>(),
169249423Sdim       AE = D->specific_attr_end<AnnotateAttr>(); AI != AE; ++AI) {
170249423Sdim    const AnnotateAttr *Ann = *AI;
171249423Sdim    if (Ann->getAnnotation() ==
172249423Sdim        "objc_allow_direct_instance_variable_assignment")
173249423Sdim      return true;
174249423Sdim  }
175249423Sdim  return false;
176249423Sdim}
177249423Sdim
178243791Sdimvoid DirectIvarAssignment::MethodCrawler::VisitBinaryOperator(
179243791Sdim                                                    const BinaryOperator *BO) {
180243791Sdim  if (!BO->isAssignmentOp())
181243791Sdim    return;
182243791Sdim
183243791Sdim  const ObjCIvarRefExpr *IvarRef =
184243791Sdim          dyn_cast<ObjCIvarRefExpr>(BO->getLHS()->IgnoreParenCasts());
185243791Sdim
186243791Sdim  if (!IvarRef)
187243791Sdim    return;
188243791Sdim
189243791Sdim  if (const ObjCIvarDecl *D = IvarRef->getDecl()) {
190243791Sdim    IvarToPropertyMapTy::const_iterator I = IvarToPropMap.find(D);
191249423Sdim
192243791Sdim    if (I != IvarToPropMap.end()) {
193243791Sdim      const ObjCPropertyDecl *PD = I->second;
194249423Sdim      // Skip warnings on Ivars, annotated with
195249423Sdim      // objc_allow_direct_instance_variable_assignment. This annotation serves
196249423Sdim      // as a false positive suppression mechanism for the checker. The
197249423Sdim      // annotation is allowed on properties and ivars.
198249423Sdim      if (isAnnotatedToAllowDirectAssignment(PD) ||
199249423Sdim          isAnnotatedToAllowDirectAssignment(D))
200249423Sdim        return;
201243791Sdim
202243791Sdim      ObjCMethodDecl *GetterMethod =
203243791Sdim          InterfD->getInstanceMethod(PD->getGetterName());
204243791Sdim      ObjCMethodDecl *SetterMethod =
205243791Sdim          InterfD->getInstanceMethod(PD->getSetterName());
206243791Sdim
207243791Sdim      if (SetterMethod && SetterMethod->getCanonicalDecl() == MD)
208243791Sdim        return;
209243791Sdim
210243791Sdim      if (GetterMethod && GetterMethod->getCanonicalDecl() == MD)
211243791Sdim        return;
212243791Sdim
213243791Sdim      BR.EmitBasicReport(MD,
214243791Sdim          "Property access",
215243791Sdim          categories::CoreFoundationObjectiveC,
216243791Sdim          "Direct assignment to an instance variable backing a property; "
217243791Sdim          "use the setter instead", PathDiagnosticLocation(IvarRef,
218243791Sdim                                                          BR.getSourceManager(),
219243791Sdim                                                          DCtx));
220243791Sdim    }
221243791Sdim  }
222243791Sdim}
223243791Sdim}
224243791Sdim
225249423Sdim// Register the checker that checks for direct accesses in all functions,
226249423Sdim// except for the initialization and copy routines.
227243791Sdimvoid ento::registerDirectIvarAssignment(CheckerManager &mgr) {
228243791Sdim  mgr.registerChecker<DirectIvarAssignment>();
229243791Sdim}
230249423Sdim
231249423Sdim// Register the checker that checks for direct accesses in functions annotated
232249423Sdim// with __attribute__((annotate("objc_no_direct_instance_variable_assignment"))).
233249423Sdimnamespace {
234249423Sdimstruct InvalidatorMethodFilter : MethodFilter {
235249423Sdim  virtual ~InvalidatorMethodFilter() {}
236249423Sdim  virtual bool operator()(ObjCMethodDecl *M) {
237249423Sdim    for (specific_attr_iterator<AnnotateAttr>
238249423Sdim         AI = M->specific_attr_begin<AnnotateAttr>(),
239249423Sdim         AE = M->specific_attr_end<AnnotateAttr>(); AI != AE; ++AI) {
240249423Sdim      const AnnotateAttr *Ann = *AI;
241249423Sdim      if (Ann->getAnnotation() == "objc_no_direct_instance_variable_assignment")
242249423Sdim        return false;
243249423Sdim    }
244249423Sdim    return true;
245249423Sdim  }
246249423Sdim};
247249423Sdim
248249423SdimInvalidatorMethodFilter AttrFilter;
249249423Sdim}
250249423Sdim
251249423Sdimvoid ento::registerDirectIvarAssignmentForAnnotatedFunctions(
252249423Sdim    CheckerManager &mgr) {
253249423Sdim  mgr.registerChecker<DirectIvarAssignment>()->ShouldSkipMethod = &AttrFilter;
254249423Sdim}
255