xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp (revision c632467e2b584f5502743edbb63db717651416a5)
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 
25 using namespace clang;
26 using namespace ento;
27 
28 namespace {
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.
35 struct 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 
49 static MethodFilter DefaultMethodFilter;
50 
51 class 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 
83 public:
84   MethodFilter *ShouldSkipMethod;
85 
86   DirectIvarAssignment() : ShouldSkipMethod(&DefaultMethodFilter) {}
87 
88   void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager& Mgr,
89                     BugReporter &BR) const;
90 };
91 
92 static 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 
114 void 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 
158 void 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.
199 void 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_assignment"))).
205 namespace {
206 struct 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_assignment")
214         return false;
215     }
216     return true;
217   }
218 };
219 
220 InvalidatorMethodFilter AttrFilter;
221 }
222 
223 void ento::registerDirectIvarAssignmentForAnnotatedFunctions(
224     CheckerManager &mgr) {
225   mgr.registerChecker<DirectIvarAssignment>()->ShouldSkipMethod = &AttrFilter;
226 }
227