xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp (revision 6519564c97d5fe113c3e6ecd2d8d2acc35fe51aa)
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 static 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 
170 void 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.
217 void 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"))).
223 namespace {
224 struct 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 
238 InvalidatorMethodFilter AttrFilter;
239 }
240 
241 void ento::registerDirectIvarAssignmentForAnnotatedFunctions(
242     CheckerManager &mgr) {
243   mgr.registerChecker<DirectIvarAssignment>()->ShouldSkipMethod = &AttrFilter;
244 }
245