1 1.1 joerg //=- DirectIvarAssignment.cpp - Check rules on ObjC properties -*- C++ ----*-==// 2 1.1 joerg // 3 1.1 joerg // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 1.1 joerg // See https://llvm.org/LICENSE.txt for license information. 5 1.1 joerg // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 1.1 joerg // 7 1.1 joerg //===----------------------------------------------------------------------===// 8 1.1 joerg // 9 1.1 joerg // Check that Objective C properties are set with the setter, not though a 10 1.1 joerg // direct assignment. 11 1.1 joerg // 12 1.1 joerg // Two versions of a checker exist: one that checks all methods and the other 13 1.1 joerg // that only checks the methods annotated with 14 1.1 joerg // __attribute__((annotate("objc_no_direct_instance_variable_assignment"))) 15 1.1 joerg // 16 1.1 joerg // The checker does not warn about assignments to Ivars, annotated with 17 1.1 joerg // __attribute__((objc_allow_direct_instance_variable_assignment"))). This 18 1.1 joerg // annotation serves as a false positive suppression mechanism for the 19 1.1 joerg // checker. The annotation is allowed on properties and Ivars. 20 1.1 joerg // 21 1.1 joerg //===----------------------------------------------------------------------===// 22 1.1 joerg 23 1.1 joerg #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" 24 1.1 joerg #include "clang/AST/Attr.h" 25 1.1 joerg #include "clang/AST/DeclObjC.h" 26 1.1 joerg #include "clang/AST/StmtVisitor.h" 27 1.1 joerg #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" 28 1.1 joerg #include "clang/StaticAnalyzer/Core/Checker.h" 29 1.1 joerg #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" 30 1.1 joerg #include "llvm/ADT/DenseMap.h" 31 1.1 joerg 32 1.1 joerg using namespace clang; 33 1.1 joerg using namespace ento; 34 1.1 joerg 35 1.1 joerg namespace { 36 1.1 joerg 37 1.1 joerg /// The default method filter, which is used to filter out the methods on which 38 1.1 joerg /// the check should not be performed. 39 1.1 joerg /// 40 1.1 joerg /// Checks for the init, dealloc, and any other functions that might be allowed 41 1.1 joerg /// to perform direct instance variable assignment based on their name. 42 1.1 joerg static bool DefaultMethodFilter(const ObjCMethodDecl *M) { 43 1.1 joerg return M->getMethodFamily() == OMF_init || 44 1.1 joerg M->getMethodFamily() == OMF_dealloc || 45 1.1 joerg M->getMethodFamily() == OMF_copy || 46 1.1 joerg M->getMethodFamily() == OMF_mutableCopy || 47 1.1 joerg M->getSelector().getNameForSlot(0).find("init") != StringRef::npos || 48 1.1 joerg M->getSelector().getNameForSlot(0).find("Init") != StringRef::npos; 49 1.1 joerg } 50 1.1 joerg 51 1.1 joerg class DirectIvarAssignment : 52 1.1 joerg public Checker<check::ASTDecl<ObjCImplementationDecl> > { 53 1.1 joerg 54 1.1 joerg typedef llvm::DenseMap<const ObjCIvarDecl*, 55 1.1 joerg const ObjCPropertyDecl*> IvarToPropertyMapTy; 56 1.1 joerg 57 1.1 joerg /// A helper class, which walks the AST and locates all assignments to ivars 58 1.1 joerg /// in the given function. 59 1.1 joerg class MethodCrawler : public ConstStmtVisitor<MethodCrawler> { 60 1.1 joerg const IvarToPropertyMapTy &IvarToPropMap; 61 1.1 joerg const ObjCMethodDecl *MD; 62 1.1 joerg const ObjCInterfaceDecl *InterfD; 63 1.1 joerg BugReporter &BR; 64 1.1 joerg const CheckerBase *Checker; 65 1.1 joerg LocationOrAnalysisDeclContext DCtx; 66 1.1 joerg 67 1.1 joerg public: 68 1.1 joerg MethodCrawler(const IvarToPropertyMapTy &InMap, const ObjCMethodDecl *InMD, 69 1.1 joerg const ObjCInterfaceDecl *InID, BugReporter &InBR, 70 1.1 joerg const CheckerBase *Checker, AnalysisDeclContext *InDCtx) 71 1.1 joerg : IvarToPropMap(InMap), MD(InMD), InterfD(InID), BR(InBR), 72 1.1 joerg Checker(Checker), DCtx(InDCtx) {} 73 1.1 joerg 74 1.1 joerg void VisitStmt(const Stmt *S) { VisitChildren(S); } 75 1.1 joerg 76 1.1 joerg void VisitBinaryOperator(const BinaryOperator *BO); 77 1.1 joerg 78 1.1 joerg void VisitChildren(const Stmt *S) { 79 1.1 joerg for (const Stmt *Child : S->children()) 80 1.1 joerg if (Child) 81 1.1 joerg this->Visit(Child); 82 1.1 joerg } 83 1.1 joerg }; 84 1.1 joerg 85 1.1 joerg public: 86 1.1 joerg bool (*ShouldSkipMethod)(const ObjCMethodDecl *); 87 1.1 joerg 88 1.1 joerg DirectIvarAssignment() : ShouldSkipMethod(&DefaultMethodFilter) {} 89 1.1 joerg 90 1.1 joerg void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager& Mgr, 91 1.1 joerg BugReporter &BR) const; 92 1.1 joerg }; 93 1.1 joerg 94 1.1 joerg static const ObjCIvarDecl *findPropertyBackingIvar(const ObjCPropertyDecl *PD, 95 1.1 joerg const ObjCInterfaceDecl *InterD, 96 1.1 joerg ASTContext &Ctx) { 97 1.1 joerg // Check for synthesized ivars. 98 1.1 joerg ObjCIvarDecl *ID = PD->getPropertyIvarDecl(); 99 1.1 joerg if (ID) 100 1.1 joerg return ID; 101 1.1 joerg 102 1.1 joerg ObjCInterfaceDecl *NonConstInterD = const_cast<ObjCInterfaceDecl*>(InterD); 103 1.1 joerg 104 1.1 joerg // Check for existing "_PropName". 105 1.1 joerg ID = NonConstInterD->lookupInstanceVariable(PD->getDefaultSynthIvarName(Ctx)); 106 1.1 joerg if (ID) 107 1.1 joerg return ID; 108 1.1 joerg 109 1.1 joerg // Check for existing "PropName". 110 1.1 joerg IdentifierInfo *PropIdent = PD->getIdentifier(); 111 1.1 joerg ID = NonConstInterD->lookupInstanceVariable(PropIdent); 112 1.1 joerg 113 1.1 joerg return ID; 114 1.1 joerg } 115 1.1 joerg 116 1.1 joerg void DirectIvarAssignment::checkASTDecl(const ObjCImplementationDecl *D, 117 1.1 joerg AnalysisManager& Mgr, 118 1.1 joerg BugReporter &BR) const { 119 1.1 joerg const ObjCInterfaceDecl *InterD = D->getClassInterface(); 120 1.1 joerg 121 1.1 joerg 122 1.1 joerg IvarToPropertyMapTy IvarToPropMap; 123 1.1 joerg 124 1.1 joerg // Find all properties for this class. 125 1.1 joerg for (const auto *PD : InterD->instance_properties()) { 126 1.1 joerg // Find the corresponding IVar. 127 1.1 joerg const ObjCIvarDecl *ID = findPropertyBackingIvar(PD, InterD, 128 1.1 joerg Mgr.getASTContext()); 129 1.1 joerg 130 1.1 joerg if (!ID) 131 1.1 joerg continue; 132 1.1 joerg 133 1.1 joerg // Store the IVar to property mapping. 134 1.1 joerg IvarToPropMap[ID] = PD; 135 1.1 joerg } 136 1.1 joerg 137 1.1 joerg if (IvarToPropMap.empty()) 138 1.1 joerg return; 139 1.1 joerg 140 1.1 joerg for (const auto *M : D->instance_methods()) { 141 1.1 joerg AnalysisDeclContext *DCtx = Mgr.getAnalysisDeclContext(M); 142 1.1 joerg 143 1.1 joerg if ((*ShouldSkipMethod)(M)) 144 1.1 joerg continue; 145 1.1 joerg 146 1.1 joerg const Stmt *Body = M->getBody(); 147 1.1.1.2 joerg if (M->isSynthesizedAccessorStub()) 148 1.1.1.2 joerg continue; 149 1.1 joerg assert(Body); 150 1.1 joerg 151 1.1 joerg MethodCrawler MC(IvarToPropMap, M->getCanonicalDecl(), InterD, BR, this, 152 1.1 joerg DCtx); 153 1.1 joerg MC.VisitStmt(Body); 154 1.1 joerg } 155 1.1 joerg } 156 1.1 joerg 157 1.1 joerg static bool isAnnotatedToAllowDirectAssignment(const Decl *D) { 158 1.1 joerg for (const auto *Ann : D->specific_attrs<AnnotateAttr>()) 159 1.1 joerg if (Ann->getAnnotation() == 160 1.1 joerg "objc_allow_direct_instance_variable_assignment") 161 1.1 joerg return true; 162 1.1 joerg return false; 163 1.1 joerg } 164 1.1 joerg 165 1.1 joerg void DirectIvarAssignment::MethodCrawler::VisitBinaryOperator( 166 1.1 joerg const BinaryOperator *BO) { 167 1.1 joerg if (!BO->isAssignmentOp()) 168 1.1 joerg return; 169 1.1 joerg 170 1.1 joerg const ObjCIvarRefExpr *IvarRef = 171 1.1 joerg dyn_cast<ObjCIvarRefExpr>(BO->getLHS()->IgnoreParenCasts()); 172 1.1 joerg 173 1.1 joerg if (!IvarRef) 174 1.1 joerg return; 175 1.1 joerg 176 1.1 joerg if (const ObjCIvarDecl *D = IvarRef->getDecl()) { 177 1.1 joerg IvarToPropertyMapTy::const_iterator I = IvarToPropMap.find(D); 178 1.1 joerg 179 1.1 joerg if (I != IvarToPropMap.end()) { 180 1.1 joerg const ObjCPropertyDecl *PD = I->second; 181 1.1 joerg // Skip warnings on Ivars, annotated with 182 1.1 joerg // objc_allow_direct_instance_variable_assignment. This annotation serves 183 1.1 joerg // as a false positive suppression mechanism for the checker. The 184 1.1 joerg // annotation is allowed on properties and ivars. 185 1.1 joerg if (isAnnotatedToAllowDirectAssignment(PD) || 186 1.1 joerg isAnnotatedToAllowDirectAssignment(D)) 187 1.1 joerg return; 188 1.1 joerg 189 1.1 joerg ObjCMethodDecl *GetterMethod = 190 1.1 joerg InterfD->getInstanceMethod(PD->getGetterName()); 191 1.1 joerg ObjCMethodDecl *SetterMethod = 192 1.1 joerg InterfD->getInstanceMethod(PD->getSetterName()); 193 1.1 joerg 194 1.1 joerg if (SetterMethod && SetterMethod->getCanonicalDecl() == MD) 195 1.1 joerg return; 196 1.1 joerg 197 1.1 joerg if (GetterMethod && GetterMethod->getCanonicalDecl() == MD) 198 1.1 joerg return; 199 1.1 joerg 200 1.1 joerg BR.EmitBasicReport( 201 1.1 joerg MD, Checker, "Property access", categories::CoreFoundationObjectiveC, 202 1.1 joerg "Direct assignment to an instance variable backing a property; " 203 1.1 joerg "use the setter instead", 204 1.1 joerg PathDiagnosticLocation(IvarRef, BR.getSourceManager(), DCtx)); 205 1.1 joerg } 206 1.1 joerg } 207 1.1 joerg } 208 1.1 joerg } 209 1.1 joerg 210 1.1 joerg // Register the checker that checks for direct accesses in functions annotated 211 1.1 joerg // with __attribute__((annotate("objc_no_direct_instance_variable_assignment"))). 212 1.1 joerg static bool AttrFilter(const ObjCMethodDecl *M) { 213 1.1 joerg for (const auto *Ann : M->specific_attrs<AnnotateAttr>()) 214 1.1 joerg if (Ann->getAnnotation() == "objc_no_direct_instance_variable_assignment") 215 1.1 joerg return false; 216 1.1 joerg return true; 217 1.1 joerg } 218 1.1 joerg 219 1.1 joerg // Register the checker that checks for direct accesses in all functions, 220 1.1 joerg // except for the initialization and copy routines. 221 1.1 joerg void ento::registerDirectIvarAssignment(CheckerManager &mgr) { 222 1.1.1.2 joerg auto Chk = mgr.registerChecker<DirectIvarAssignment>(); 223 1.1.1.2 joerg if (mgr.getAnalyzerOptions().getCheckerBooleanOption(Chk, 224 1.1.1.2 joerg "AnnotatedFunctions")) 225 1.1.1.2 joerg Chk->ShouldSkipMethod = &AttrFilter; 226 1.1 joerg } 227 1.1 joerg 228 1.1.1.2 joerg bool ento::shouldRegisterDirectIvarAssignment(const CheckerManager &mgr) { 229 1.1 joerg return true; 230 1.1 joerg } 231