1 //=- DirectIvarAssignment.cpp - Check rules on ObjC properties -*- C++ ----*-==//
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
7 //===----------------------------------------------------------------------===//
9 // Check that Objective C properties are set with the setter, not though a
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")))
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.
21 //===----------------------------------------------------------------------===//
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"
32 using namespace clang
;
37 /// The default method filter, which is used to filter out the methods on which
38 /// the check should not be performed.
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.
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");
51 class DirectIvarAssignment
:
52 public Checker
<check::ASTDecl
<ObjCImplementationDecl
> > {
54 typedef llvm::DenseMap
<const ObjCIvarDecl
*,
55 const ObjCPropertyDecl
*> IvarToPropertyMapTy
;
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
;
64 const CheckerBase
*Checker
;
65 LocationOrAnalysisDeclContext DCtx
;
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
) {}
74 void VisitStmt(const Stmt
*S
) { VisitChildren(S
); }
76 void VisitBinaryOperator(const BinaryOperator
*BO
);
78 void VisitChildren(const Stmt
*S
) {
79 for (const Stmt
*Child
: S
->children())
86 bool (*ShouldSkipMethod
)(const ObjCMethodDecl
*);
88 DirectIvarAssignment() : ShouldSkipMethod(&DefaultMethodFilter
) {}
90 void checkASTDecl(const ObjCImplementationDecl
*D
, AnalysisManager
& Mgr
,
91 BugReporter
&BR
) const;
94 static const ObjCIvarDecl
*findPropertyBackingIvar(const ObjCPropertyDecl
*PD
,
95 const ObjCInterfaceDecl
*InterD
,
97 // Check for synthesized ivars.
98 ObjCIvarDecl
*ID
= PD
->getPropertyIvarDecl();
102 ObjCInterfaceDecl
*NonConstInterD
= const_cast<ObjCInterfaceDecl
*>(InterD
);
104 // Check for existing "_PropName".
105 ID
= NonConstInterD
->lookupInstanceVariable(PD
->getDefaultSynthIvarName(Ctx
));
109 // Check for existing "PropName".
110 IdentifierInfo
*PropIdent
= PD
->getIdentifier();
111 ID
= NonConstInterD
->lookupInstanceVariable(PropIdent
);
116 void DirectIvarAssignment::checkASTDecl(const ObjCImplementationDecl
*D
,
117 AnalysisManager
& Mgr
,
118 BugReporter
&BR
) const {
119 const ObjCInterfaceDecl
*InterD
= D
->getClassInterface();
122 IvarToPropertyMapTy IvarToPropMap
;
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());
133 // Store the IVar to property mapping.
134 IvarToPropMap
[ID
] = PD
;
137 if (IvarToPropMap
.empty())
140 for (const auto *M
: D
->instance_methods()) {
141 AnalysisDeclContext
*DCtx
= Mgr
.getAnalysisDeclContext(M
);
143 if ((*ShouldSkipMethod
)(M
))
146 const Stmt
*Body
= M
->getBody();
147 if (M
->isSynthesizedAccessorStub())
151 MethodCrawler
MC(IvarToPropMap
, M
->getCanonicalDecl(), InterD
, BR
, this,
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")
165 void DirectIvarAssignment::MethodCrawler::VisitBinaryOperator(
166 const BinaryOperator
*BO
) {
167 if (!BO
->isAssignmentOp())
170 const ObjCIvarRefExpr
*IvarRef
=
171 dyn_cast
<ObjCIvarRefExpr
>(BO
->getLHS()->IgnoreParenCasts());
176 if (const ObjCIvarDecl
*D
= IvarRef
->getDecl()) {
177 IvarToPropertyMapTy::const_iterator I
= IvarToPropMap
.find(D
);
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
))
189 ObjCMethodDecl
*GetterMethod
=
190 InterfD
->getInstanceMethod(PD
->getGetterName());
191 ObjCMethodDecl
*SetterMethod
=
192 InterfD
->getInstanceMethod(PD
->getSetterName());
194 if (SetterMethod
&& SetterMethod
->getCanonicalDecl() == MD
)
197 if (GetterMethod
&& GetterMethod
->getCanonicalDecl() == MD
)
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
));
210 // Register the checker that checks for direct accesses in functions annotated
211 // with __attribute__((annotate("objc_no_direct_instance_variable_assignment"))).
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")
219 // Register the checker that checks for direct accesses in all functions,
220 // except for the initialization and copy routines.
221 void ento::registerDirectIvarAssignment(CheckerManager
&mgr
) {
222 auto Chk
= mgr
.registerChecker
<DirectIvarAssignment
>();
223 if (mgr
.getAnalyzerOptions().getCheckerBooleanOption(Chk
,
224 "AnnotatedFunctions"))
225 Chk
->ShouldSkipMethod
= &AttrFilter
;
228 bool ento::shouldRegisterDirectIvarAssignment(const CheckerManager
&mgr
) {