1243791Sdim//=- DirectIvarAssignment.cpp - Check rules on ObjC properties -*- C++ ----*-==// 2243791Sdim// 3243791Sdim// The LLVM Compiler Infrastructure 4243791Sdim// 5243791Sdim// This file is distributed under the University of Illinois Open Source 6243791Sdim// License. See LICENSE.TXT for details. 7243791Sdim// 8243791Sdim//===----------------------------------------------------------------------===// 9243791Sdim// 10249423Sdim// Check that Objective C properties are set with the setter, not though a 11249423Sdim// direct assignment. 12243791Sdim// 13249423Sdim// Two versions of a checker exist: one that checks all methods and the other 14249423Sdim// that only checks the methods annotated with 15249423Sdim// __attribute__((annotate("objc_no_direct_instance_variable_assignment"))) 16249423Sdim// 17249423Sdim// The checker does not warn about assignments to Ivars, annotated with 18249423Sdim// __attribute__((objc_allow_direct_instance_variable_assignment"))). This 19249423Sdim// annotation serves as a false positive suppression mechanism for the 20249423Sdim// checker. The annotation is allowed on properties and Ivars. 21249423Sdim// 22243791Sdim//===----------------------------------------------------------------------===// 23243791Sdim 24243791Sdim#include "ClangSACheckers.h" 25249423Sdim#include "clang/AST/Attr.h" 26249423Sdim#include "clang/AST/DeclObjC.h" 27249423Sdim#include "clang/AST/StmtVisitor.h" 28249423Sdim#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" 29243791Sdim#include "clang/StaticAnalyzer/Core/Checker.h" 30243791Sdim#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" 31243791Sdim#include "llvm/ADT/DenseMap.h" 32243791Sdim 33243791Sdimusing namespace clang; 34243791Sdimusing namespace ento; 35243791Sdim 36243791Sdimnamespace { 37243791Sdim 38249423Sdim/// The default method filter, which is used to filter out the methods on which 39249423Sdim/// the check should not be performed. 40249423Sdim/// 41249423Sdim/// Checks for the init, dealloc, and any other functions that might be allowed 42249423Sdim/// to perform direct instance variable assignment based on their name. 43261991Sdimstatic bool DefaultMethodFilter(const ObjCMethodDecl *M) { 44296417Sdim return M->getMethodFamily() == OMF_init || 45296417Sdim M->getMethodFamily() == OMF_dealloc || 46296417Sdim M->getMethodFamily() == OMF_copy || 47296417Sdim M->getMethodFamily() == OMF_mutableCopy || 48296417Sdim M->getSelector().getNameForSlot(0).find("init") != StringRef::npos || 49296417Sdim M->getSelector().getNameForSlot(0).find("Init") != StringRef::npos; 50261991Sdim} 51249423Sdim 52243791Sdimclass DirectIvarAssignment : 53243791Sdim public Checker<check::ASTDecl<ObjCImplementationDecl> > { 54243791Sdim 55243791Sdim typedef llvm::DenseMap<const ObjCIvarDecl*, 56243791Sdim const ObjCPropertyDecl*> IvarToPropertyMapTy; 57243791Sdim 58243791Sdim /// A helper class, which walks the AST and locates all assignments to ivars 59243791Sdim /// in the given function. 60243791Sdim class MethodCrawler : public ConstStmtVisitor<MethodCrawler> { 61243791Sdim const IvarToPropertyMapTy &IvarToPropMap; 62243791Sdim const ObjCMethodDecl *MD; 63243791Sdim const ObjCInterfaceDecl *InterfD; 64243791Sdim BugReporter &BR; 65276479Sdim const CheckerBase *Checker; 66243791Sdim LocationOrAnalysisDeclContext DCtx; 67243791Sdim 68243791Sdim public: 69243791Sdim MethodCrawler(const IvarToPropertyMapTy &InMap, const ObjCMethodDecl *InMD, 70276479Sdim const ObjCInterfaceDecl *InID, BugReporter &InBR, 71276479Sdim const CheckerBase *Checker, AnalysisDeclContext *InDCtx) 72276479Sdim : IvarToPropMap(InMap), MD(InMD), InterfD(InID), BR(InBR), 73276479Sdim Checker(Checker), DCtx(InDCtx) {} 74243791Sdim 75243791Sdim void VisitStmt(const Stmt *S) { VisitChildren(S); } 76243791Sdim 77243791Sdim void VisitBinaryOperator(const BinaryOperator *BO); 78243791Sdim 79243791Sdim void VisitChildren(const Stmt *S) { 80288943Sdim for (const Stmt *Child : S->children()) 81288943Sdim if (Child) 82288943Sdim this->Visit(Child); 83243791Sdim } 84243791Sdim }; 85243791Sdim 86243791Sdimpublic: 87261991Sdim bool (*ShouldSkipMethod)(const ObjCMethodDecl *); 88249423Sdim 89249423Sdim DirectIvarAssignment() : ShouldSkipMethod(&DefaultMethodFilter) {} 90249423Sdim 91243791Sdim void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager& Mgr, 92243791Sdim BugReporter &BR) const; 93243791Sdim}; 94243791Sdim 95243791Sdimstatic const ObjCIvarDecl *findPropertyBackingIvar(const ObjCPropertyDecl *PD, 96243791Sdim const ObjCInterfaceDecl *InterD, 97243791Sdim ASTContext &Ctx) { 98243791Sdim // Check for synthesized ivars. 99243791Sdim ObjCIvarDecl *ID = PD->getPropertyIvarDecl(); 100243791Sdim if (ID) 101243791Sdim return ID; 102243791Sdim 103243791Sdim ObjCInterfaceDecl *NonConstInterD = const_cast<ObjCInterfaceDecl*>(InterD); 104243791Sdim 105243791Sdim // Check for existing "_PropName". 106243791Sdim ID = NonConstInterD->lookupInstanceVariable(PD->getDefaultSynthIvarName(Ctx)); 107243791Sdim if (ID) 108243791Sdim return ID; 109243791Sdim 110243791Sdim // Check for existing "PropName". 111243791Sdim IdentifierInfo *PropIdent = PD->getIdentifier(); 112243791Sdim ID = NonConstInterD->lookupInstanceVariable(PropIdent); 113243791Sdim 114243791Sdim return ID; 115243791Sdim} 116243791Sdim 117243791Sdimvoid DirectIvarAssignment::checkASTDecl(const ObjCImplementationDecl *D, 118243791Sdim AnalysisManager& Mgr, 119243791Sdim BugReporter &BR) const { 120243791Sdim const ObjCInterfaceDecl *InterD = D->getClassInterface(); 121243791Sdim 122243791Sdim 123243791Sdim IvarToPropertyMapTy IvarToPropMap; 124243791Sdim 125243791Sdim // Find all properties for this class. 126276479Sdim for (const auto *PD : InterD->properties()) { 127243791Sdim // Find the corresponding IVar. 128243791Sdim const ObjCIvarDecl *ID = findPropertyBackingIvar(PD, InterD, 129243791Sdim Mgr.getASTContext()); 130243791Sdim 131243791Sdim if (!ID) 132243791Sdim continue; 133243791Sdim 134243791Sdim // Store the IVar to property mapping. 135243791Sdim IvarToPropMap[ID] = PD; 136243791Sdim } 137243791Sdim 138243791Sdim if (IvarToPropMap.empty()) 139243791Sdim return; 140243791Sdim 141276479Sdim for (const auto *M : D->instance_methods()) { 142243791Sdim AnalysisDeclContext *DCtx = Mgr.getAnalysisDeclContext(M); 143243791Sdim 144249423Sdim if ((*ShouldSkipMethod)(M)) 145243791Sdim continue; 146243791Sdim 147243791Sdim const Stmt *Body = M->getBody(); 148243791Sdim assert(Body); 149243791Sdim 150276479Sdim MethodCrawler MC(IvarToPropMap, M->getCanonicalDecl(), InterD, BR, this, 151276479Sdim DCtx); 152243791Sdim MC.VisitStmt(Body); 153243791Sdim } 154243791Sdim} 155243791Sdim 156249423Sdimstatic bool isAnnotatedToAllowDirectAssignment(const Decl *D) { 157276479Sdim for (const auto *Ann : D->specific_attrs<AnnotateAttr>()) 158249423Sdim if (Ann->getAnnotation() == 159249423Sdim "objc_allow_direct_instance_variable_assignment") 160249423Sdim return true; 161249423Sdim return false; 162249423Sdim} 163249423Sdim 164243791Sdimvoid DirectIvarAssignment::MethodCrawler::VisitBinaryOperator( 165243791Sdim const BinaryOperator *BO) { 166243791Sdim if (!BO->isAssignmentOp()) 167243791Sdim return; 168243791Sdim 169243791Sdim const ObjCIvarRefExpr *IvarRef = 170243791Sdim dyn_cast<ObjCIvarRefExpr>(BO->getLHS()->IgnoreParenCasts()); 171243791Sdim 172243791Sdim if (!IvarRef) 173243791Sdim return; 174243791Sdim 175243791Sdim if (const ObjCIvarDecl *D = IvarRef->getDecl()) { 176243791Sdim IvarToPropertyMapTy::const_iterator I = IvarToPropMap.find(D); 177249423Sdim 178243791Sdim if (I != IvarToPropMap.end()) { 179243791Sdim const ObjCPropertyDecl *PD = I->second; 180249423Sdim // Skip warnings on Ivars, annotated with 181249423Sdim // objc_allow_direct_instance_variable_assignment. This annotation serves 182249423Sdim // as a false positive suppression mechanism for the checker. The 183249423Sdim // annotation is allowed on properties and ivars. 184249423Sdim if (isAnnotatedToAllowDirectAssignment(PD) || 185249423Sdim isAnnotatedToAllowDirectAssignment(D)) 186249423Sdim return; 187243791Sdim 188243791Sdim ObjCMethodDecl *GetterMethod = 189243791Sdim InterfD->getInstanceMethod(PD->getGetterName()); 190243791Sdim ObjCMethodDecl *SetterMethod = 191243791Sdim InterfD->getInstanceMethod(PD->getSetterName()); 192243791Sdim 193243791Sdim if (SetterMethod && SetterMethod->getCanonicalDecl() == MD) 194243791Sdim return; 195243791Sdim 196243791Sdim if (GetterMethod && GetterMethod->getCanonicalDecl() == MD) 197243791Sdim return; 198243791Sdim 199276479Sdim BR.EmitBasicReport( 200276479Sdim MD, Checker, "Property access", categories::CoreFoundationObjectiveC, 201243791Sdim "Direct assignment to an instance variable backing a property; " 202276479Sdim "use the setter instead", 203276479Sdim PathDiagnosticLocation(IvarRef, BR.getSourceManager(), DCtx)); 204243791Sdim } 205243791Sdim } 206243791Sdim} 207243791Sdim} 208243791Sdim 209249423Sdim// Register the checker that checks for direct accesses in all functions, 210249423Sdim// except for the initialization and copy routines. 211243791Sdimvoid ento::registerDirectIvarAssignment(CheckerManager &mgr) { 212243791Sdim mgr.registerChecker<DirectIvarAssignment>(); 213243791Sdim} 214249423Sdim 215249423Sdim// Register the checker that checks for direct accesses in functions annotated 216249423Sdim// with __attribute__((annotate("objc_no_direct_instance_variable_assignment"))). 217261991Sdimstatic bool AttrFilter(const ObjCMethodDecl *M) { 218276479Sdim for (const auto *Ann : M->specific_attrs<AnnotateAttr>()) 219261991Sdim if (Ann->getAnnotation() == "objc_no_direct_instance_variable_assignment") 220261991Sdim return false; 221261991Sdim return true; 222249423Sdim} 223249423Sdim 224249423Sdimvoid ento::registerDirectIvarAssignmentForAnnotatedFunctions( 225249423Sdim CheckerManager &mgr) { 226249423Sdim mgr.registerChecker<DirectIvarAssignment>()->ShouldSkipMethod = &AttrFilter; 227249423Sdim} 228