//=- DirectIvarAssignment.cpp - Check rules on ObjC properties -*- C++ ----*-==// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // Check that Objective C properties are set with the setter, not though a // direct assignment. // // Two versions of a checker exist: one that checks all methods and the other // that only checks the methods annotated with // __attribute__((annotate("objc_no_direct_instance_variable_assignment"))) // // The checker does not warn about assignments to Ivars, annotated with // __attribute__((objc_allow_direct_instance_variable_assignment"))). This // annotation serves as a false positive suppression mechanism for the // checker. The annotation is allowed on properties and Ivars. // //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/Attr.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/StmtVisitor.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "llvm/ADT/DenseMap.h" using namespace clang; using namespace ento; namespace { /// The default method filter, which is used to filter out the methods on which /// the check should not be performed. /// /// Checks for the init, dealloc, and any other functions that might be allowed /// to perform direct instance variable assignment based on their name. static bool DefaultMethodFilter(const ObjCMethodDecl *M) { return M->getMethodFamily() == OMF_init || M->getMethodFamily() == OMF_dealloc || M->getMethodFamily() == OMF_copy || M->getMethodFamily() == OMF_mutableCopy || M->getSelector().getNameForSlot(0).contains("init") || M->getSelector().getNameForSlot(0).contains("Init"); } class DirectIvarAssignment : public Checker > { typedef llvm::DenseMap IvarToPropertyMapTy; /// A helper class, which walks the AST and locates all assignments to ivars /// in the given function. class MethodCrawler : public ConstStmtVisitor { const IvarToPropertyMapTy &IvarToPropMap; const ObjCMethodDecl *MD; const ObjCInterfaceDecl *InterfD; BugReporter &BR; const CheckerBase *Checker; LocationOrAnalysisDeclContext DCtx; public: MethodCrawler(const IvarToPropertyMapTy &InMap, const ObjCMethodDecl *InMD, const ObjCInterfaceDecl *InID, BugReporter &InBR, const CheckerBase *Checker, AnalysisDeclContext *InDCtx) : IvarToPropMap(InMap), MD(InMD), InterfD(InID), BR(InBR), Checker(Checker), DCtx(InDCtx) {} void VisitStmt(const Stmt *S) { VisitChildren(S); } void VisitBinaryOperator(const BinaryOperator *BO); void VisitChildren(const Stmt *S) { for (const Stmt *Child : S->children()) if (Child) this->Visit(Child); } }; public: bool (*ShouldSkipMethod)(const ObjCMethodDecl *); DirectIvarAssignment() : ShouldSkipMethod(&DefaultMethodFilter) {} void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager& Mgr, BugReporter &BR) const; }; static const ObjCIvarDecl *findPropertyBackingIvar(const ObjCPropertyDecl *PD, const ObjCInterfaceDecl *InterD, ASTContext &Ctx) { // Check for synthesized ivars. ObjCIvarDecl *ID = PD->getPropertyIvarDecl(); if (ID) return ID; ObjCInterfaceDecl *NonConstInterD = const_cast(InterD); // Check for existing "_PropName". ID = NonConstInterD->lookupInstanceVariable(PD->getDefaultSynthIvarName(Ctx)); if (ID) return ID; // Check for existing "PropName". IdentifierInfo *PropIdent = PD->getIdentifier(); ID = NonConstInterD->lookupInstanceVariable(PropIdent); return ID; } void DirectIvarAssignment::checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager& Mgr, BugReporter &BR) const { const ObjCInterfaceDecl *InterD = D->getClassInterface(); IvarToPropertyMapTy IvarToPropMap; // Find all properties for this class. for (const auto *PD : InterD->instance_properties()) { // Find the corresponding IVar. const ObjCIvarDecl *ID = findPropertyBackingIvar(PD, InterD, Mgr.getASTContext()); if (!ID) continue; // Store the IVar to property mapping. IvarToPropMap[ID] = PD; } if (IvarToPropMap.empty()) return; for (const auto *M : D->instance_methods()) { AnalysisDeclContext *DCtx = Mgr.getAnalysisDeclContext(M); if ((*ShouldSkipMethod)(M)) continue; const Stmt *Body = M->getBody(); if (M->isSynthesizedAccessorStub()) continue; assert(Body); MethodCrawler MC(IvarToPropMap, M->getCanonicalDecl(), InterD, BR, this, DCtx); MC.VisitStmt(Body); } } static bool isAnnotatedToAllowDirectAssignment(const Decl *D) { for (const auto *Ann : D->specific_attrs()) if (Ann->getAnnotation() == "objc_allow_direct_instance_variable_assignment") return true; return false; } void DirectIvarAssignment::MethodCrawler::VisitBinaryOperator( const BinaryOperator *BO) { if (!BO->isAssignmentOp()) return; const ObjCIvarRefExpr *IvarRef = dyn_cast(BO->getLHS()->IgnoreParenCasts()); if (!IvarRef) return; if (const ObjCIvarDecl *D = IvarRef->getDecl()) { IvarToPropertyMapTy::const_iterator I = IvarToPropMap.find(D); if (I != IvarToPropMap.end()) { const ObjCPropertyDecl *PD = I->second; // Skip warnings on Ivars, annotated with // objc_allow_direct_instance_variable_assignment. This annotation serves // as a false positive suppression mechanism for the checker. The // annotation is allowed on properties and ivars. if (isAnnotatedToAllowDirectAssignment(PD) || isAnnotatedToAllowDirectAssignment(D)) return; ObjCMethodDecl *GetterMethod = InterfD->getInstanceMethod(PD->getGetterName()); ObjCMethodDecl *SetterMethod = InterfD->getInstanceMethod(PD->getSetterName()); if (SetterMethod && SetterMethod->getCanonicalDecl() == MD) return; if (GetterMethod && GetterMethod->getCanonicalDecl() == MD) return; BR.EmitBasicReport( MD, Checker, "Property access", categories::CoreFoundationObjectiveC, "Direct assignment to an instance variable backing a property; " "use the setter instead", PathDiagnosticLocation(IvarRef, BR.getSourceManager(), DCtx)); } } } } // Register the checker that checks for direct accesses in functions annotated // with __attribute__((annotate("objc_no_direct_instance_variable_assignment"))). static bool AttrFilter(const ObjCMethodDecl *M) { for (const auto *Ann : M->specific_attrs()) if (Ann->getAnnotation() == "objc_no_direct_instance_variable_assignment") return false; return true; } // Register the checker that checks for direct accesses in all functions, // except for the initialization and copy routines. void ento::registerDirectIvarAssignment(CheckerManager &mgr) { auto Chk = mgr.registerChecker(); if (mgr.getAnalyzerOptions().getCheckerBooleanOption(Chk, "AnnotatedFunctions")) Chk->ShouldSkipMethod = &AttrFilter; } bool ento::shouldRegisterDirectIvarAssignment(const CheckerManager &mgr) { return true; }