1461f2393SAnna Zaks //=- DirectIvarAssignment.cpp - Check rules on ObjC properties -*- C++ ----*-==//
2461f2393SAnna Zaks //
32946cd70SChandler Carruth // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
42946cd70SChandler Carruth // See https://llvm.org/LICENSE.txt for license information.
52946cd70SChandler Carruth // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6461f2393SAnna Zaks //
7461f2393SAnna Zaks //===----------------------------------------------------------------------===//
8461f2393SAnna Zaks //
90e9c9419SAnna Zaks //  Check that Objective C properties are set with the setter, not though a
100e9c9419SAnna Zaks //      direct assignment.
110e9c9419SAnna Zaks //
120e9c9419SAnna Zaks //  Two versions of a checker exist: one that checks all methods and the other
130e9c9419SAnna Zaks //      that only checks the methods annotated with
140e9c9419SAnna Zaks //      __attribute__((annotate("objc_no_direct_instance_variable_assignment")))
150e9c9419SAnna Zaks //
160e9c9419SAnna Zaks //  The checker does not warn about assignments to Ivars, annotated with
170e9c9419SAnna Zaks //       __attribute__((objc_allow_direct_instance_variable_assignment"))). This
180e9c9419SAnna Zaks //      annotation serves as a false positive suppression mechanism for the
190e9c9419SAnna Zaks //      checker. The annotation is allowed on properties and Ivars.
20461f2393SAnna Zaks //
21461f2393SAnna Zaks //===----------------------------------------------------------------------===//
22461f2393SAnna Zaks 
2376a21502SKristof Umann #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
2425dd07c1SAnna Zaks #include "clang/AST/Attr.h"
25461f2393SAnna Zaks #include "clang/AST/DeclObjC.h"
26461f2393SAnna Zaks #include "clang/AST/StmtVisitor.h"
273a02247dSChandler Carruth #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
283a02247dSChandler Carruth #include "clang/StaticAnalyzer/Core/Checker.h"
293a02247dSChandler Carruth #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
30461f2393SAnna Zaks #include "llvm/ADT/DenseMap.h"
31461f2393SAnna Zaks 
32461f2393SAnna Zaks using namespace clang;
33461f2393SAnna Zaks using namespace ento;
34461f2393SAnna Zaks 
35461f2393SAnna Zaks namespace {
36461f2393SAnna Zaks 
3725dd07c1SAnna Zaks /// The default method filter, which is used to filter out the methods on which
3825dd07c1SAnna Zaks /// the check should not be performed.
3925dd07c1SAnna Zaks ///
4025dd07c1SAnna Zaks /// Checks for the init, dealloc, and any other functions that might be allowed
4125dd07c1SAnna Zaks /// to perform direct instance variable assignment based on their name.
DefaultMethodFilter(const ObjCMethodDecl * M)4227bc5042SBenjamin Kramer static bool DefaultMethodFilter(const ObjCMethodDecl *M) {
439c10490eSAlexander Kornienko   return M->getMethodFamily() == OMF_init ||
449c10490eSAlexander Kornienko          M->getMethodFamily() == OMF_dealloc ||
4525dd07c1SAnna Zaks          M->getMethodFamily() == OMF_copy ||
4625dd07c1SAnna Zaks          M->getMethodFamily() == OMF_mutableCopy ||
47*0abb5d29SKazu Hirata          M->getSelector().getNameForSlot(0).contains("init") ||
48*0abb5d29SKazu Hirata          M->getSelector().getNameForSlot(0).contains("Init");
4925dd07c1SAnna Zaks }
5025dd07c1SAnna Zaks 
51461f2393SAnna Zaks class DirectIvarAssignment :
52461f2393SAnna Zaks   public Checker<check::ASTDecl<ObjCImplementationDecl> > {
53461f2393SAnna Zaks 
54461f2393SAnna Zaks   typedef llvm::DenseMap<const ObjCIvarDecl*,
55461f2393SAnna Zaks                          const ObjCPropertyDecl*> IvarToPropertyMapTy;
56461f2393SAnna Zaks 
57461f2393SAnna Zaks   /// A helper class, which walks the AST and locates all assignments to ivars
58461f2393SAnna Zaks   /// in the given function.
59461f2393SAnna Zaks   class MethodCrawler : public ConstStmtVisitor<MethodCrawler> {
60461f2393SAnna Zaks     const IvarToPropertyMapTy &IvarToPropMap;
61461f2393SAnna Zaks     const ObjCMethodDecl *MD;
62461f2393SAnna Zaks     const ObjCInterfaceDecl *InterfD;
63461f2393SAnna Zaks     BugReporter &BR;
644aca9b1cSAlexander Kornienko     const CheckerBase *Checker;
65461f2393SAnna Zaks     LocationOrAnalysisDeclContext DCtx;
66461f2393SAnna Zaks 
67461f2393SAnna Zaks   public:
MethodCrawler(const IvarToPropertyMapTy & InMap,const ObjCMethodDecl * InMD,const ObjCInterfaceDecl * InID,BugReporter & InBR,const CheckerBase * Checker,AnalysisDeclContext * InDCtx)68461f2393SAnna Zaks     MethodCrawler(const IvarToPropertyMapTy &InMap, const ObjCMethodDecl *InMD,
694aca9b1cSAlexander Kornienko                   const ObjCInterfaceDecl *InID, BugReporter &InBR,
704aca9b1cSAlexander Kornienko                   const CheckerBase *Checker, AnalysisDeclContext *InDCtx)
714aca9b1cSAlexander Kornienko         : IvarToPropMap(InMap), MD(InMD), InterfD(InID), BR(InBR),
724aca9b1cSAlexander Kornienko           Checker(Checker), DCtx(InDCtx) {}
73461f2393SAnna Zaks 
VisitStmt(const Stmt * S)74461f2393SAnna Zaks     void VisitStmt(const Stmt *S) { VisitChildren(S); }
75461f2393SAnna Zaks 
76461f2393SAnna Zaks     void VisitBinaryOperator(const BinaryOperator *BO);
77461f2393SAnna Zaks 
VisitChildren(const Stmt * S)78461f2393SAnna Zaks     void VisitChildren(const Stmt *S) {
79642f173aSBenjamin Kramer       for (const Stmt *Child : S->children())
80642f173aSBenjamin Kramer         if (Child)
81642f173aSBenjamin Kramer           this->Visit(Child);
82461f2393SAnna Zaks     }
83461f2393SAnna Zaks   };
84461f2393SAnna Zaks 
85461f2393SAnna Zaks public:
8627bc5042SBenjamin Kramer   bool (*ShouldSkipMethod)(const ObjCMethodDecl *);
8725dd07c1SAnna Zaks 
DirectIvarAssignment()8825dd07c1SAnna Zaks   DirectIvarAssignment() : ShouldSkipMethod(&DefaultMethodFilter) {}
8925dd07c1SAnna Zaks 
90461f2393SAnna Zaks   void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager& Mgr,
91461f2393SAnna Zaks                     BugReporter &BR) const;
92461f2393SAnna Zaks };
93461f2393SAnna Zaks 
findPropertyBackingIvar(const ObjCPropertyDecl * PD,const ObjCInterfaceDecl * InterD,ASTContext & Ctx)94461f2393SAnna Zaks static const ObjCIvarDecl *findPropertyBackingIvar(const ObjCPropertyDecl *PD,
956aef4555SAnna Zaks                                                const ObjCInterfaceDecl *InterD,
96461f2393SAnna Zaks                                                ASTContext &Ctx) {
97461f2393SAnna Zaks   // Check for synthesized ivars.
98461f2393SAnna Zaks   ObjCIvarDecl *ID = PD->getPropertyIvarDecl();
99461f2393SAnna Zaks   if (ID)
100461f2393SAnna Zaks     return ID;
101461f2393SAnna Zaks 
1026aef4555SAnna Zaks   ObjCInterfaceDecl *NonConstInterD = const_cast<ObjCInterfaceDecl*>(InterD);
1036aef4555SAnna Zaks 
104461f2393SAnna Zaks   // Check for existing "_PropName".
1056aef4555SAnna Zaks   ID = NonConstInterD->lookupInstanceVariable(PD->getDefaultSynthIvarName(Ctx));
106461f2393SAnna Zaks   if (ID)
107461f2393SAnna Zaks     return ID;
108461f2393SAnna Zaks 
109461f2393SAnna Zaks   // Check for existing "PropName".
110461f2393SAnna Zaks   IdentifierInfo *PropIdent = PD->getIdentifier();
1116aef4555SAnna Zaks   ID = NonConstInterD->lookupInstanceVariable(PropIdent);
112461f2393SAnna Zaks 
113461f2393SAnna Zaks   return ID;
114461f2393SAnna Zaks }
115461f2393SAnna Zaks 
checkASTDecl(const ObjCImplementationDecl * D,AnalysisManager & Mgr,BugReporter & BR) const116461f2393SAnna Zaks void DirectIvarAssignment::checkASTDecl(const ObjCImplementationDecl *D,
117461f2393SAnna Zaks                                        AnalysisManager& Mgr,
118461f2393SAnna Zaks                                        BugReporter &BR) const {
119461f2393SAnna Zaks   const ObjCInterfaceDecl *InterD = D->getClassInterface();
120461f2393SAnna Zaks 
121461f2393SAnna Zaks 
122461f2393SAnna Zaks   IvarToPropertyMapTy IvarToPropMap;
123461f2393SAnna Zaks 
124461f2393SAnna Zaks   // Find all properties for this class.
125a7a8b1f2SManman Ren   for (const auto *PD : InterD->instance_properties()) {
126461f2393SAnna Zaks     // Find the corresponding IVar.
1276aef4555SAnna Zaks     const ObjCIvarDecl *ID = findPropertyBackingIvar(PD, InterD,
128461f2393SAnna Zaks                                                      Mgr.getASTContext());
129461f2393SAnna Zaks 
130461f2393SAnna Zaks     if (!ID)
131461f2393SAnna Zaks       continue;
132461f2393SAnna Zaks 
133461f2393SAnna Zaks     // Store the IVar to property mapping.
134461f2393SAnna Zaks     IvarToPropMap[ID] = PD;
135461f2393SAnna Zaks   }
136461f2393SAnna Zaks 
137461f2393SAnna Zaks   if (IvarToPropMap.empty())
138461f2393SAnna Zaks     return;
139461f2393SAnna Zaks 
140f26acce6SAaron Ballman   for (const auto *M : D->instance_methods()) {
141461f2393SAnna Zaks     AnalysisDeclContext *DCtx = Mgr.getAnalysisDeclContext(M);
142461f2393SAnna Zaks 
14325dd07c1SAnna Zaks     if ((*ShouldSkipMethod)(M))
144461f2393SAnna Zaks       continue;
145461f2393SAnna Zaks 
146461f2393SAnna Zaks     const Stmt *Body = M->getBody();
1472073dd2dSAdrian Prantl     if (M->isSynthesizedAccessorStub())
1482073dd2dSAdrian Prantl       continue;
1496aef4555SAnna Zaks     assert(Body);
150461f2393SAnna Zaks 
1514aca9b1cSAlexander Kornienko     MethodCrawler MC(IvarToPropMap, M->getCanonicalDecl(), InterD, BR, this,
1524aca9b1cSAlexander Kornienko                      DCtx);
153461f2393SAnna Zaks     MC.VisitStmt(Body);
154461f2393SAnna Zaks   }
155461f2393SAnna Zaks }
156461f2393SAnna Zaks 
isAnnotatedToAllowDirectAssignment(const Decl * D)1570e9c9419SAnna Zaks static bool isAnnotatedToAllowDirectAssignment(const Decl *D) {
158be22bcb1SAaron Ballman   for (const auto *Ann : D->specific_attrs<AnnotateAttr>())
1596519564cSAnna Zaks     if (Ann->getAnnotation() ==
1606519564cSAnna Zaks         "objc_allow_direct_instance_variable_assignment")
1616519564cSAnna Zaks       return true;
1626519564cSAnna Zaks   return false;
1636519564cSAnna Zaks }
1646519564cSAnna Zaks 
VisitBinaryOperator(const BinaryOperator * BO)165461f2393SAnna Zaks void DirectIvarAssignment::MethodCrawler::VisitBinaryOperator(
166461f2393SAnna Zaks                                                     const BinaryOperator *BO) {
167461f2393SAnna Zaks   if (!BO->isAssignmentOp())
168461f2393SAnna Zaks     return;
169461f2393SAnna Zaks 
1706aef4555SAnna Zaks   const ObjCIvarRefExpr *IvarRef =
1716aef4555SAnna Zaks           dyn_cast<ObjCIvarRefExpr>(BO->getLHS()->IgnoreParenCasts());
172461f2393SAnna Zaks 
173461f2393SAnna Zaks   if (!IvarRef)
174461f2393SAnna Zaks     return;
175461f2393SAnna Zaks 
176461f2393SAnna Zaks   if (const ObjCIvarDecl *D = IvarRef->getDecl()) {
177461f2393SAnna Zaks     IvarToPropertyMapTy::const_iterator I = IvarToPropMap.find(D);
1786519564cSAnna Zaks 
179461f2393SAnna Zaks     if (I != IvarToPropMap.end()) {
180461f2393SAnna Zaks       const ObjCPropertyDecl *PD = I->second;
1810e9c9419SAnna Zaks       // Skip warnings on Ivars, annotated with
1826519564cSAnna Zaks       // objc_allow_direct_instance_variable_assignment. This annotation serves
1830e9c9419SAnna Zaks       // as a false positive suppression mechanism for the checker. The
1840e9c9419SAnna Zaks       // annotation is allowed on properties and ivars.
1850e9c9419SAnna Zaks       if (isAnnotatedToAllowDirectAssignment(PD) ||
1860e9c9419SAnna Zaks           isAnnotatedToAllowDirectAssignment(D))
1876519564cSAnna Zaks         return;
188461f2393SAnna Zaks 
189461f2393SAnna Zaks       ObjCMethodDecl *GetterMethod =
190461f2393SAnna Zaks           InterfD->getInstanceMethod(PD->getGetterName());
191461f2393SAnna Zaks       ObjCMethodDecl *SetterMethod =
192461f2393SAnna Zaks           InterfD->getInstanceMethod(PD->getSetterName());
193461f2393SAnna Zaks 
194461f2393SAnna Zaks       if (SetterMethod && SetterMethod->getCanonicalDecl() == MD)
195461f2393SAnna Zaks         return;
196461f2393SAnna Zaks 
197461f2393SAnna Zaks       if (GetterMethod && GetterMethod->getCanonicalDecl() == MD)
198461f2393SAnna Zaks         return;
199461f2393SAnna Zaks 
2004aca9b1cSAlexander Kornienko       BR.EmitBasicReport(
2014aca9b1cSAlexander Kornienko           MD, Checker, "Property access", categories::CoreFoundationObjectiveC,
202461f2393SAnna Zaks           "Direct assignment to an instance variable backing a property; "
2034aca9b1cSAlexander Kornienko           "use the setter instead",
2044aca9b1cSAlexander Kornienko           PathDiagnosticLocation(IvarRef, BR.getSourceManager(), DCtx));
205461f2393SAnna Zaks     }
206461f2393SAnna Zaks   }
207461f2393SAnna Zaks }
208ab9db510SAlexander Kornienko }
209461f2393SAnna Zaks 
21025dd07c1SAnna Zaks // Register the checker that checks for direct accesses in functions annotated
211c632467eSTed Kremenek // with __attribute__((annotate("objc_no_direct_instance_variable_assignment"))).
AttrFilter(const ObjCMethodDecl * M)21227bc5042SBenjamin Kramer static bool AttrFilter(const ObjCMethodDecl *M) {
213be22bcb1SAaron Ballman   for (const auto *Ann : M->specific_attrs<AnnotateAttr>())
214c632467eSTed Kremenek     if (Ann->getAnnotation() == "objc_no_direct_instance_variable_assignment")
21525dd07c1SAnna Zaks       return false;
21625dd07c1SAnna Zaks   return true;
21725dd07c1SAnna Zaks }
21825dd07c1SAnna Zaks 
219058a7a45SKristof Umann // Register the checker that checks for direct accesses in all functions,
220058a7a45SKristof Umann // except for the initialization and copy routines.
registerDirectIvarAssignment(CheckerManager & mgr)221058a7a45SKristof Umann void ento::registerDirectIvarAssignment(CheckerManager &mgr) {
222500479dbSKirstóf Umann   auto Chk = mgr.registerChecker<DirectIvarAssignment>();
223500479dbSKirstóf Umann   if (mgr.getAnalyzerOptions().getCheckerBooleanOption(Chk,
224500479dbSKirstóf Umann                                                        "AnnotatedFunctions"))
225500479dbSKirstóf Umann     Chk->ShouldSkipMethod = &AttrFilter;
226058a7a45SKristof Umann }
227058a7a45SKristof Umann 
shouldRegisterDirectIvarAssignment(const CheckerManager & mgr)228bda3dd0dSKirstóf Umann bool ento::shouldRegisterDirectIvarAssignment(const CheckerManager &mgr) {
229058a7a45SKristof Umann   return true;
230058a7a45SKristof Umann }
231