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.
43261991Sdimstatic bool DefaultMethodFilter(const ObjCMethodDecl *M) {
44296417Sdim  return M->getMethodFamily() == OMF_init ||
45296417Sdim         M->getMethodFamily() == OMF_dealloc ||
46296417Sdim         M->getMethodFamily() == OMF_copy ||
47296417Sdim         M->getMethodFamily() == OMF_mutableCopy ||
48296417Sdim         M->getSelector().getNameForSlot(0).find("init") != StringRef::npos ||
49296417Sdim         M->getSelector().getNameForSlot(0).find("Init") != StringRef::npos;
50261991Sdim}
51249423Sdim
52243791Sdimclass DirectIvarAssignment :
53243791Sdim  public Checker<check::ASTDecl<ObjCImplementationDecl> > {
54243791Sdim
55243791Sdim  typedef llvm::DenseMap<const ObjCIvarDecl*,
56243791Sdim                         const ObjCPropertyDecl*> IvarToPropertyMapTy;
57243791Sdim
58243791Sdim  /// A helper class, which walks the AST and locates all assignments to ivars
59243791Sdim  /// in the given function.
60243791Sdim  class MethodCrawler : public ConstStmtVisitor<MethodCrawler> {
61243791Sdim    const IvarToPropertyMapTy &IvarToPropMap;
62243791Sdim    const ObjCMethodDecl *MD;
63243791Sdim    const ObjCInterfaceDecl *InterfD;
64243791Sdim    BugReporter &BR;
65276479Sdim    const CheckerBase *Checker;
66243791Sdim    LocationOrAnalysisDeclContext DCtx;
67243791Sdim
68243791Sdim  public:
69243791Sdim    MethodCrawler(const IvarToPropertyMapTy &InMap, const ObjCMethodDecl *InMD,
70276479Sdim                  const ObjCInterfaceDecl *InID, BugReporter &InBR,
71276479Sdim                  const CheckerBase *Checker, AnalysisDeclContext *InDCtx)
72276479Sdim        : IvarToPropMap(InMap), MD(InMD), InterfD(InID), BR(InBR),
73276479Sdim          Checker(Checker), DCtx(InDCtx) {}
74243791Sdim
75243791Sdim    void VisitStmt(const Stmt *S) { VisitChildren(S); }
76243791Sdim
77243791Sdim    void VisitBinaryOperator(const BinaryOperator *BO);
78243791Sdim
79243791Sdim    void VisitChildren(const Stmt *S) {
80288943Sdim      for (const Stmt *Child : S->children())
81288943Sdim        if (Child)
82288943Sdim          this->Visit(Child);
83243791Sdim    }
84243791Sdim  };
85243791Sdim
86243791Sdimpublic:
87261991Sdim  bool (*ShouldSkipMethod)(const ObjCMethodDecl *);
88249423Sdim
89249423Sdim  DirectIvarAssignment() : ShouldSkipMethod(&DefaultMethodFilter) {}
90249423Sdim
91243791Sdim  void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager& Mgr,
92243791Sdim                    BugReporter &BR) const;
93243791Sdim};
94243791Sdim
95243791Sdimstatic const ObjCIvarDecl *findPropertyBackingIvar(const ObjCPropertyDecl *PD,
96243791Sdim                                               const ObjCInterfaceDecl *InterD,
97243791Sdim                                               ASTContext &Ctx) {
98243791Sdim  // Check for synthesized ivars.
99243791Sdim  ObjCIvarDecl *ID = PD->getPropertyIvarDecl();
100243791Sdim  if (ID)
101243791Sdim    return ID;
102243791Sdim
103243791Sdim  ObjCInterfaceDecl *NonConstInterD = const_cast<ObjCInterfaceDecl*>(InterD);
104243791Sdim
105243791Sdim  // Check for existing "_PropName".
106243791Sdim  ID = NonConstInterD->lookupInstanceVariable(PD->getDefaultSynthIvarName(Ctx));
107243791Sdim  if (ID)
108243791Sdim    return ID;
109243791Sdim
110243791Sdim  // Check for existing "PropName".
111243791Sdim  IdentifierInfo *PropIdent = PD->getIdentifier();
112243791Sdim  ID = NonConstInterD->lookupInstanceVariable(PropIdent);
113243791Sdim
114243791Sdim  return ID;
115243791Sdim}
116243791Sdim
117243791Sdimvoid DirectIvarAssignment::checkASTDecl(const ObjCImplementationDecl *D,
118243791Sdim                                       AnalysisManager& Mgr,
119243791Sdim                                       BugReporter &BR) const {
120243791Sdim  const ObjCInterfaceDecl *InterD = D->getClassInterface();
121243791Sdim
122243791Sdim
123243791Sdim  IvarToPropertyMapTy IvarToPropMap;
124243791Sdim
125243791Sdim  // Find all properties for this class.
126276479Sdim  for (const auto *PD : InterD->properties()) {
127243791Sdim    // Find the corresponding IVar.
128243791Sdim    const ObjCIvarDecl *ID = findPropertyBackingIvar(PD, InterD,
129243791Sdim                                                     Mgr.getASTContext());
130243791Sdim
131243791Sdim    if (!ID)
132243791Sdim      continue;
133243791Sdim
134243791Sdim    // Store the IVar to property mapping.
135243791Sdim    IvarToPropMap[ID] = PD;
136243791Sdim  }
137243791Sdim
138243791Sdim  if (IvarToPropMap.empty())
139243791Sdim    return;
140243791Sdim
141276479Sdim  for (const auto *M : D->instance_methods()) {
142243791Sdim    AnalysisDeclContext *DCtx = Mgr.getAnalysisDeclContext(M);
143243791Sdim
144249423Sdim    if ((*ShouldSkipMethod)(M))
145243791Sdim      continue;
146243791Sdim
147243791Sdim    const Stmt *Body = M->getBody();
148243791Sdim    assert(Body);
149243791Sdim
150276479Sdim    MethodCrawler MC(IvarToPropMap, M->getCanonicalDecl(), InterD, BR, this,
151276479Sdim                     DCtx);
152243791Sdim    MC.VisitStmt(Body);
153243791Sdim  }
154243791Sdim}
155243791Sdim
156249423Sdimstatic bool isAnnotatedToAllowDirectAssignment(const Decl *D) {
157276479Sdim  for (const auto *Ann : D->specific_attrs<AnnotateAttr>())
158249423Sdim    if (Ann->getAnnotation() ==
159249423Sdim        "objc_allow_direct_instance_variable_assignment")
160249423Sdim      return true;
161249423Sdim  return false;
162249423Sdim}
163249423Sdim
164243791Sdimvoid DirectIvarAssignment::MethodCrawler::VisitBinaryOperator(
165243791Sdim                                                    const BinaryOperator *BO) {
166243791Sdim  if (!BO->isAssignmentOp())
167243791Sdim    return;
168243791Sdim
169243791Sdim  const ObjCIvarRefExpr *IvarRef =
170243791Sdim          dyn_cast<ObjCIvarRefExpr>(BO->getLHS()->IgnoreParenCasts());
171243791Sdim
172243791Sdim  if (!IvarRef)
173243791Sdim    return;
174243791Sdim
175243791Sdim  if (const ObjCIvarDecl *D = IvarRef->getDecl()) {
176243791Sdim    IvarToPropertyMapTy::const_iterator I = IvarToPropMap.find(D);
177249423Sdim
178243791Sdim    if (I != IvarToPropMap.end()) {
179243791Sdim      const ObjCPropertyDecl *PD = I->second;
180249423Sdim      // Skip warnings on Ivars, annotated with
181249423Sdim      // objc_allow_direct_instance_variable_assignment. This annotation serves
182249423Sdim      // as a false positive suppression mechanism for the checker. The
183249423Sdim      // annotation is allowed on properties and ivars.
184249423Sdim      if (isAnnotatedToAllowDirectAssignment(PD) ||
185249423Sdim          isAnnotatedToAllowDirectAssignment(D))
186249423Sdim        return;
187243791Sdim
188243791Sdim      ObjCMethodDecl *GetterMethod =
189243791Sdim          InterfD->getInstanceMethod(PD->getGetterName());
190243791Sdim      ObjCMethodDecl *SetterMethod =
191243791Sdim          InterfD->getInstanceMethod(PD->getSetterName());
192243791Sdim
193243791Sdim      if (SetterMethod && SetterMethod->getCanonicalDecl() == MD)
194243791Sdim        return;
195243791Sdim
196243791Sdim      if (GetterMethod && GetterMethod->getCanonicalDecl() == MD)
197243791Sdim        return;
198243791Sdim
199276479Sdim      BR.EmitBasicReport(
200276479Sdim          MD, Checker, "Property access", categories::CoreFoundationObjectiveC,
201243791Sdim          "Direct assignment to an instance variable backing a property; "
202276479Sdim          "use the setter instead",
203276479Sdim          PathDiagnosticLocation(IvarRef, BR.getSourceManager(), DCtx));
204243791Sdim    }
205243791Sdim  }
206243791Sdim}
207243791Sdim}
208243791Sdim
209249423Sdim// Register the checker that checks for direct accesses in all functions,
210249423Sdim// except for the initialization and copy routines.
211243791Sdimvoid ento::registerDirectIvarAssignment(CheckerManager &mgr) {
212243791Sdim  mgr.registerChecker<DirectIvarAssignment>();
213243791Sdim}
214249423Sdim
215249423Sdim// Register the checker that checks for direct accesses in functions annotated
216249423Sdim// with __attribute__((annotate("objc_no_direct_instance_variable_assignment"))).
217261991Sdimstatic bool AttrFilter(const ObjCMethodDecl *M) {
218276479Sdim  for (const auto *Ann : M->specific_attrs<AnnotateAttr>())
219261991Sdim    if (Ann->getAnnotation() == "objc_no_direct_instance_variable_assignment")
220261991Sdim      return false;
221261991Sdim  return true;
222249423Sdim}
223249423Sdim
224249423Sdimvoid ento::registerDirectIvarAssignmentForAnnotatedFunctions(
225249423Sdim    CheckerManager &mgr) {
226249423Sdim  mgr.registerChecker<DirectIvarAssignment>()->ShouldSkipMethod = &AttrFilter;
227249423Sdim}
228