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