231 lines · 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-exception6//7//===----------------------------------------------------------------------===//8//9// Check that Objective C properties are set with the setter, not though a10// direct assignment.11//12// Two versions of a checker exist: one that checks all methods and the other13// that only checks the methods annotated with14// __attribute__((annotate("objc_no_direct_instance_variable_assignment")))15//16// The checker does not warn about assignments to Ivars, annotated with17// __attribute__((objc_allow_direct_instance_variable_assignment"))). This18// annotation serves as a false positive suppression mechanism for the19// 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 which38/// the check should not be performed.39///40/// Checks for the init, dealloc, and any other functions that might be allowed41/// 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).contains("init") ||48 M->getSelector().getNameForSlot(0).contains("Init");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 ivars58 /// 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 &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 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 *D, AnalysisManager& Mgr,91 BugReporter &BR) const;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 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 157static 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 165void 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 with182 // objc_allow_direct_instance_variable_assignment. This annotation serves183 // as a false positive suppression mechanism for the checker. The184 // 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 annotated211// with __attribute__((annotate("objc_no_direct_instance_variable_assignment"))).212static 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.221void ento::registerDirectIvarAssignment(CheckerManager &mgr) {222 auto Chk = mgr.registerChecker<DirectIvarAssignment>();223 if (mgr.getAnalyzerOptions().getCheckerBooleanOption(Chk,224 "AnnotatedFunctions"))225 Chk->ShouldSkipMethod = &AttrFilter;226}227 228bool ento::shouldRegisterDirectIvarAssignment(const CheckerManager &mgr) {229 return true;230}231