Clang Project

clang_source_code/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp
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
32using namespace clang;
33using namespace ento;
34
35namespace {
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.
42static 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).find("init") != StringRef::npos ||
48         M->getSelector().getNameForSlot(0).find("Init") != StringRef::npos;
49}
50
51class 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:
68    MethodCrawler(const IvarToPropertyMapTy &InMapconst ObjCMethodDecl *InMD,
69                  const ObjCInterfaceDecl *InIDBugReporter &InBR,
70                  const CheckerBase *CheckerAnalysisDeclContext *InDCtx)
71        : IvarToPropMap(InMap), MD(InMD), InterfD(InID), BR(InBR),
72          Checker(Checker), DCtx(InDCtx) {}
73
74    void VisitStmt(const Stmt *S) { VisitChildren(S); }
75
76    void VisitBinaryOperator(const BinaryOperator *BO);
77
78    void VisitChildren(const Stmt *S) {
79      for (const Stmt *Child : S->children())
80        if (Child)
81          this->Visit(Child);
82    }
83  };
84
85public:
86  bool (*ShouldSkipMethod)(const ObjCMethodDecl *);
87
88  DirectIvarAssignment() : ShouldSkipMethod(&DefaultMethodFilter) {}
89
90  void checkASTDecl(const ObjCImplementationDecl *DAnalysisManagerMgr,
91                    BugReporter &BRconst;
92};
93
94static 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
116void DirectIvarAssignment::checkASTDecl(const ObjCImplementationDecl *D,
117                                       AnalysisManagerMgr,
118                                       BugReporter &BRconst {
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    assert(Body);
148
149    MethodCrawler MC(IvarToPropMap, M->getCanonicalDecl(), InterD, BR, this,
150                     DCtx);
151    MC.VisitStmt(Body);
152  }
153}
154
155static bool isAnnotatedToAllowDirectAssignment(const Decl *D) {
156  for (const auto *Ann : D->specific_attrs<AnnotateAttr>())
157    if (Ann->getAnnotation() ==
158        "objc_allow_direct_instance_variable_assignment")
159      return true;
160  return false;
161}
162
163void DirectIvarAssignment::MethodCrawler::VisitBinaryOperator(
164                                                    const BinaryOperator *BO) {
165  if (!BO->isAssignmentOp())
166    return;
167
168  const ObjCIvarRefExpr *IvarRef =
169          dyn_cast<ObjCIvarRefExpr>(BO->getLHS()->IgnoreParenCasts());
170
171  if (!IvarRef)
172    return;
173
174  if (const ObjCIvarDecl *D = IvarRef->getDecl()) {
175    IvarToPropertyMapTy::const_iterator I = IvarToPropMap.find(D);
176
177    if (I != IvarToPropMap.end()) {
178      const ObjCPropertyDecl *PD = I->second;
179      // Skip warnings on Ivars, annotated with
180      // objc_allow_direct_instance_variable_assignment. This annotation serves
181      // as a false positive suppression mechanism for the checker. The
182      // annotation is allowed on properties and ivars.
183      if (isAnnotatedToAllowDirectAssignment(PD) ||
184          isAnnotatedToAllowDirectAssignment(D))
185        return;
186
187      ObjCMethodDecl *GetterMethod =
188          InterfD->getInstanceMethod(PD->getGetterName());
189      ObjCMethodDecl *SetterMethod =
190          InterfD->getInstanceMethod(PD->getSetterName());
191
192      if (SetterMethod && SetterMethod->getCanonicalDecl() == MD)
193        return;
194
195      if (GetterMethod && GetterMethod->getCanonicalDecl() == MD)
196        return;
197
198      BR.EmitBasicReport(
199          MD, Checker, "Property access", categories::CoreFoundationObjectiveC,
200          "Direct assignment to an instance variable backing a property; "
201          "use the setter instead",
202          PathDiagnosticLocation(IvarRef, BR.getSourceManager(), DCtx));
203    }
204  }
205}
206}
207
208// Register the checker that checks for direct accesses in functions annotated
209// with __attribute__((annotate("objc_no_direct_instance_variable_assignment"))).
210static bool AttrFilter(const ObjCMethodDecl *M) {
211  for (const auto *Ann : M->specific_attrs<AnnotateAttr>())
212    if (Ann->getAnnotation() == "objc_no_direct_instance_variable_assignment")
213      return false;
214  return true;
215}
216
217// Register the checker that checks for direct accesses in all functions,
218// except for the initialization and copy routines.
219void ento::registerDirectIvarAssignment(CheckerManager &mgr) {
220  mgr.registerChecker<DirectIvarAssignment>();
221}
222
223bool ento::shouldRegisterDirectIvarAssignment(const LangOptions &LO) {
224  return true;
225}
226
227void ento::registerDirectIvarAssignmentForAnnotatedFunctions(
228    CheckerManager &mgr) {
229  mgr.getChecker<DirectIvarAssignment>()->ShouldSkipMethod = &AttrFilter;
230}
231
232bool ento::shouldRegisterDirectIvarAssignmentForAnnotatedFunctions(
233                                                        const LangOptions &LO) {
234  return true;
235}
236