xref: /freebsd/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp (revision 0fca6ea1d4eea4c934cfff25ac9ee8ad6fe95583)
1 //===- DirectIvarAssignment.cpp - Check rules on ObjC properties -*- C++ -*-==//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 //  Check that Objective C properties are set with the setter, not though a
10 //      direct assignment.
11 //
12 //  Two versions of a checker exist: one that checks all methods and the other
13 //      that only checks the methods annotated with
14 //      __attribute__((annotate("objc_no_direct_instance_variable_assignment")))
15 //
16 //  The checker does not warn about assignments to Ivars, annotated with
17 //       __attribute__((objc_allow_direct_instance_variable_assignment"))). This
18 //      annotation serves as a false positive suppression mechanism for the
19 //      checker. The annotation is allowed on properties and Ivars.
20 //
21 //===----------------------------------------------------------------------===//
22 
23 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
24 #include "clang/AST/Attr.h"
25 #include "clang/AST/DeclObjC.h"
26 #include "clang/AST/StmtVisitor.h"
27 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
28 #include "clang/StaticAnalyzer/Core/Checker.h"
29 #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
30 #include "llvm/ADT/DenseMap.h"
31 
32 using namespace clang;
33 using namespace ento;
34 
35 namespace {
36 
37 /// The default method filter, which is used to filter out the methods on which
38 /// the check should not be performed.
39 ///
40 /// Checks for the init, dealloc, and any other functions that might be allowed
41 /// to perform direct instance variable assignment based on their name.
DefaultMethodFilter(const ObjCMethodDecl * M)42 static bool DefaultMethodFilter(const ObjCMethodDecl *M) {
43   return M->getMethodFamily() == OMF_init ||
44          M->getMethodFamily() == OMF_dealloc ||
45          M->getMethodFamily() == OMF_copy ||
46          M->getMethodFamily() == OMF_mutableCopy ||
47          M->getSelector().getNameForSlot(0).contains("init") ||
48          M->getSelector().getNameForSlot(0).contains("Init");
49 }
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     const CheckerBase *Checker;
65     LocationOrAnalysisDeclContext DCtx;
66 
67   public:
MethodCrawler(const IvarToPropertyMapTy & InMap,const ObjCMethodDecl * InMD,const ObjCInterfaceDecl * InID,BugReporter & InBR,const CheckerBase * Checker,AnalysisDeclContext * InDCtx)68     MethodCrawler(const IvarToPropertyMapTy &InMap, const ObjCMethodDecl *InMD,
69                   const ObjCInterfaceDecl *InID, BugReporter &InBR,
70                   const CheckerBase *Checker, AnalysisDeclContext *InDCtx)
71         : IvarToPropMap(InMap), MD(InMD), InterfD(InID), BR(InBR),
72           Checker(Checker), DCtx(InDCtx) {}
73 
VisitStmt(const Stmt * S)74     void VisitStmt(const Stmt *S) { VisitChildren(S); }
75 
76     void VisitBinaryOperator(const BinaryOperator *BO);
77 
VisitChildren(const Stmt * S)78     void VisitChildren(const Stmt *S) {
79       for (const Stmt *Child : S->children())
80         if (Child)
81           this->Visit(Child);
82     }
83   };
84 
85 public:
86   bool (*ShouldSkipMethod)(const ObjCMethodDecl *);
87 
DirectIvarAssignment()88   DirectIvarAssignment() : ShouldSkipMethod(&DefaultMethodFilter) {}
89 
90   void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager& Mgr,
91                     BugReporter &BR) const;
92 };
93 
findPropertyBackingIvar(const ObjCPropertyDecl * PD,const ObjCInterfaceDecl * InterD,ASTContext & Ctx)94 static const ObjCIvarDecl *findPropertyBackingIvar(const ObjCPropertyDecl *PD,
95                                                const ObjCInterfaceDecl *InterD,
96                                                ASTContext &Ctx) {
97   // Check for synthesized ivars.
98   ObjCIvarDecl *ID = PD->getPropertyIvarDecl();
99   if (ID)
100     return ID;
101 
102   ObjCInterfaceDecl *NonConstInterD = const_cast<ObjCInterfaceDecl*>(InterD);
103 
104   // Check for existing "_PropName".
105   ID = NonConstInterD->lookupInstanceVariable(PD->getDefaultSynthIvarName(Ctx));
106   if (ID)
107     return ID;
108 
109   // Check for existing "PropName".
110   IdentifierInfo *PropIdent = PD->getIdentifier();
111   ID = NonConstInterD->lookupInstanceVariable(PropIdent);
112 
113   return ID;
114 }
115 
checkASTDecl(const ObjCImplementationDecl * D,AnalysisManager & Mgr,BugReporter & BR) const116 void DirectIvarAssignment::checkASTDecl(const ObjCImplementationDecl *D,
117                                        AnalysisManager& Mgr,
118                                        BugReporter &BR) const {
119   const ObjCInterfaceDecl *InterD = D->getClassInterface();
120 
121 
122   IvarToPropertyMapTy IvarToPropMap;
123 
124   // Find all properties for this class.
125   for (const auto *PD : InterD->instance_properties()) {
126     // Find the corresponding IVar.
127     const ObjCIvarDecl *ID = findPropertyBackingIvar(PD, InterD,
128                                                      Mgr.getASTContext());
129 
130     if (!ID)
131       continue;
132 
133     // Store the IVar to property mapping.
134     IvarToPropMap[ID] = PD;
135   }
136 
137   if (IvarToPropMap.empty())
138     return;
139 
140   for (const auto *M : D->instance_methods()) {
141     AnalysisDeclContext *DCtx = Mgr.getAnalysisDeclContext(M);
142 
143     if ((*ShouldSkipMethod)(M))
144       continue;
145 
146     const Stmt *Body = M->getBody();
147     if (M->isSynthesizedAccessorStub())
148       continue;
149     assert(Body);
150 
151     MethodCrawler MC(IvarToPropMap, M->getCanonicalDecl(), InterD, BR, this,
152                      DCtx);
153     MC.VisitStmt(Body);
154   }
155 }
156 
isAnnotatedToAllowDirectAssignment(const Decl * D)157 static bool isAnnotatedToAllowDirectAssignment(const Decl *D) {
158   for (const auto *Ann : D->specific_attrs<AnnotateAttr>())
159     if (Ann->getAnnotation() ==
160         "objc_allow_direct_instance_variable_assignment")
161       return true;
162   return false;
163 }
164 
VisitBinaryOperator(const BinaryOperator * BO)165 void DirectIvarAssignment::MethodCrawler::VisitBinaryOperator(
166                                                     const BinaryOperator *BO) {
167   if (!BO->isAssignmentOp())
168     return;
169 
170   const ObjCIvarRefExpr *IvarRef =
171           dyn_cast<ObjCIvarRefExpr>(BO->getLHS()->IgnoreParenCasts());
172 
173   if (!IvarRef)
174     return;
175 
176   if (const ObjCIvarDecl *D = IvarRef->getDecl()) {
177     IvarToPropertyMapTy::const_iterator I = IvarToPropMap.find(D);
178 
179     if (I != IvarToPropMap.end()) {
180       const ObjCPropertyDecl *PD = I->second;
181       // Skip warnings on Ivars, annotated with
182       // objc_allow_direct_instance_variable_assignment. This annotation serves
183       // as a false positive suppression mechanism for the checker. The
184       // annotation is allowed on properties and ivars.
185       if (isAnnotatedToAllowDirectAssignment(PD) ||
186           isAnnotatedToAllowDirectAssignment(D))
187         return;
188 
189       ObjCMethodDecl *GetterMethod =
190           InterfD->getInstanceMethod(PD->getGetterName());
191       ObjCMethodDecl *SetterMethod =
192           InterfD->getInstanceMethod(PD->getSetterName());
193 
194       if (SetterMethod && SetterMethod->getCanonicalDecl() == MD)
195         return;
196 
197       if (GetterMethod && GetterMethod->getCanonicalDecl() == MD)
198         return;
199 
200       BR.EmitBasicReport(
201           MD, Checker, "Property access", categories::CoreFoundationObjectiveC,
202           "Direct assignment to an instance variable backing a property; "
203           "use the setter instead",
204           PathDiagnosticLocation(IvarRef, BR.getSourceManager(), DCtx));
205     }
206   }
207 }
208 }
209 
210 // Register the checker that checks for direct accesses in functions annotated
211 // with __attribute__((annotate("objc_no_direct_instance_variable_assignment"))).
AttrFilter(const ObjCMethodDecl * M)212 static bool AttrFilter(const ObjCMethodDecl *M) {
213   for (const auto *Ann : M->specific_attrs<AnnotateAttr>())
214     if (Ann->getAnnotation() == "objc_no_direct_instance_variable_assignment")
215       return false;
216   return true;
217 }
218 
219 // Register the checker that checks for direct accesses in all functions,
220 // except for the initialization and copy routines.
registerDirectIvarAssignment(CheckerManager & mgr)221 void ento::registerDirectIvarAssignment(CheckerManager &mgr) {
222   auto Chk = mgr.registerChecker<DirectIvarAssignment>();
223   if (mgr.getAnalyzerOptions().getCheckerBooleanOption(Chk,
224                                                        "AnnotatedFunctions"))
225     Chk->ShouldSkipMethod = &AttrFilter;
226 }
227 
shouldRegisterDirectIvarAssignment(const CheckerManager & mgr)228 bool ento::shouldRegisterDirectIvarAssignment(const CheckerManager &mgr) {
229   return true;
230 }
231