xref: /freebsd-src/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp (revision 0fca6ea1d4eea4c934cfff25ac9ee8ad6fe95583)
1*0fca6ea1SDimitry Andric //===- DirectIvarAssignment.cpp - Check rules on ObjC properties -*- C++ -*-==//
20b57cec5SDimitry Andric //
30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
60b57cec5SDimitry Andric //
70b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
80b57cec5SDimitry Andric //
90b57cec5SDimitry Andric //  Check that Objective C properties are set with the setter, not though a
100b57cec5SDimitry Andric //      direct assignment.
110b57cec5SDimitry Andric //
120b57cec5SDimitry Andric //  Two versions of a checker exist: one that checks all methods and the other
130b57cec5SDimitry Andric //      that only checks the methods annotated with
140b57cec5SDimitry Andric //      __attribute__((annotate("objc_no_direct_instance_variable_assignment")))
150b57cec5SDimitry Andric //
160b57cec5SDimitry Andric //  The checker does not warn about assignments to Ivars, annotated with
170b57cec5SDimitry Andric //       __attribute__((objc_allow_direct_instance_variable_assignment"))). This
180b57cec5SDimitry Andric //      annotation serves as a false positive suppression mechanism for the
190b57cec5SDimitry Andric //      checker. The annotation is allowed on properties and Ivars.
200b57cec5SDimitry Andric //
210b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
220b57cec5SDimitry Andric 
230b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
240b57cec5SDimitry Andric #include "clang/AST/Attr.h"
250b57cec5SDimitry Andric #include "clang/AST/DeclObjC.h"
260b57cec5SDimitry Andric #include "clang/AST/StmtVisitor.h"
270b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
280b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/Checker.h"
290b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
300b57cec5SDimitry Andric #include "llvm/ADT/DenseMap.h"
310b57cec5SDimitry Andric 
320b57cec5SDimitry Andric using namespace clang;
330b57cec5SDimitry Andric using namespace ento;
340b57cec5SDimitry Andric 
350b57cec5SDimitry Andric namespace {
360b57cec5SDimitry Andric 
370b57cec5SDimitry Andric /// The default method filter, which is used to filter out the methods on which
380b57cec5SDimitry Andric /// the check should not be performed.
390b57cec5SDimitry Andric ///
400b57cec5SDimitry Andric /// Checks for the init, dealloc, and any other functions that might be allowed
410b57cec5SDimitry Andric /// to perform direct instance variable assignment based on their name.
420b57cec5SDimitry Andric static bool DefaultMethodFilter(const ObjCMethodDecl *M) {
430b57cec5SDimitry Andric   return M->getMethodFamily() == OMF_init ||
440b57cec5SDimitry Andric          M->getMethodFamily() == OMF_dealloc ||
450b57cec5SDimitry Andric          M->getMethodFamily() == OMF_copy ||
460b57cec5SDimitry Andric          M->getMethodFamily() == OMF_mutableCopy ||
47349cc55cSDimitry Andric          M->getSelector().getNameForSlot(0).contains("init") ||
48349cc55cSDimitry Andric          M->getSelector().getNameForSlot(0).contains("Init");
490b57cec5SDimitry Andric }
500b57cec5SDimitry Andric 
510b57cec5SDimitry Andric class DirectIvarAssignment :
520b57cec5SDimitry Andric   public Checker<check::ASTDecl<ObjCImplementationDecl> > {
530b57cec5SDimitry Andric 
540b57cec5SDimitry Andric   typedef llvm::DenseMap<const ObjCIvarDecl*,
550b57cec5SDimitry Andric                          const ObjCPropertyDecl*> IvarToPropertyMapTy;
560b57cec5SDimitry Andric 
570b57cec5SDimitry Andric   /// A helper class, which walks the AST and locates all assignments to ivars
580b57cec5SDimitry Andric   /// in the given function.
590b57cec5SDimitry Andric   class MethodCrawler : public ConstStmtVisitor<MethodCrawler> {
600b57cec5SDimitry Andric     const IvarToPropertyMapTy &IvarToPropMap;
610b57cec5SDimitry Andric     const ObjCMethodDecl *MD;
620b57cec5SDimitry Andric     const ObjCInterfaceDecl *InterfD;
630b57cec5SDimitry Andric     BugReporter &BR;
640b57cec5SDimitry Andric     const CheckerBase *Checker;
650b57cec5SDimitry Andric     LocationOrAnalysisDeclContext DCtx;
660b57cec5SDimitry Andric 
670b57cec5SDimitry Andric   public:
680b57cec5SDimitry Andric     MethodCrawler(const IvarToPropertyMapTy &InMap, const ObjCMethodDecl *InMD,
690b57cec5SDimitry Andric                   const ObjCInterfaceDecl *InID, BugReporter &InBR,
700b57cec5SDimitry Andric                   const CheckerBase *Checker, AnalysisDeclContext *InDCtx)
710b57cec5SDimitry Andric         : IvarToPropMap(InMap), MD(InMD), InterfD(InID), BR(InBR),
720b57cec5SDimitry Andric           Checker(Checker), DCtx(InDCtx) {}
730b57cec5SDimitry Andric 
740b57cec5SDimitry Andric     void VisitStmt(const Stmt *S) { VisitChildren(S); }
750b57cec5SDimitry Andric 
760b57cec5SDimitry Andric     void VisitBinaryOperator(const BinaryOperator *BO);
770b57cec5SDimitry Andric 
780b57cec5SDimitry Andric     void VisitChildren(const Stmt *S) {
790b57cec5SDimitry Andric       for (const Stmt *Child : S->children())
800b57cec5SDimitry Andric         if (Child)
810b57cec5SDimitry Andric           this->Visit(Child);
820b57cec5SDimitry Andric     }
830b57cec5SDimitry Andric   };
840b57cec5SDimitry Andric 
850b57cec5SDimitry Andric public:
860b57cec5SDimitry Andric   bool (*ShouldSkipMethod)(const ObjCMethodDecl *);
870b57cec5SDimitry Andric 
880b57cec5SDimitry Andric   DirectIvarAssignment() : ShouldSkipMethod(&DefaultMethodFilter) {}
890b57cec5SDimitry Andric 
900b57cec5SDimitry Andric   void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager& Mgr,
910b57cec5SDimitry Andric                     BugReporter &BR) const;
920b57cec5SDimitry Andric };
930b57cec5SDimitry Andric 
940b57cec5SDimitry Andric static const ObjCIvarDecl *findPropertyBackingIvar(const ObjCPropertyDecl *PD,
950b57cec5SDimitry Andric                                                const ObjCInterfaceDecl *InterD,
960b57cec5SDimitry Andric                                                ASTContext &Ctx) {
970b57cec5SDimitry Andric   // Check for synthesized ivars.
980b57cec5SDimitry Andric   ObjCIvarDecl *ID = PD->getPropertyIvarDecl();
990b57cec5SDimitry Andric   if (ID)
1000b57cec5SDimitry Andric     return ID;
1010b57cec5SDimitry Andric 
1020b57cec5SDimitry Andric   ObjCInterfaceDecl *NonConstInterD = const_cast<ObjCInterfaceDecl*>(InterD);
1030b57cec5SDimitry Andric 
1040b57cec5SDimitry Andric   // Check for existing "_PropName".
1050b57cec5SDimitry Andric   ID = NonConstInterD->lookupInstanceVariable(PD->getDefaultSynthIvarName(Ctx));
1060b57cec5SDimitry Andric   if (ID)
1070b57cec5SDimitry Andric     return ID;
1080b57cec5SDimitry Andric 
1090b57cec5SDimitry Andric   // Check for existing "PropName".
1100b57cec5SDimitry Andric   IdentifierInfo *PropIdent = PD->getIdentifier();
1110b57cec5SDimitry Andric   ID = NonConstInterD->lookupInstanceVariable(PropIdent);
1120b57cec5SDimitry Andric 
1130b57cec5SDimitry Andric   return ID;
1140b57cec5SDimitry Andric }
1150b57cec5SDimitry Andric 
1160b57cec5SDimitry Andric void DirectIvarAssignment::checkASTDecl(const ObjCImplementationDecl *D,
1170b57cec5SDimitry Andric                                        AnalysisManager& Mgr,
1180b57cec5SDimitry Andric                                        BugReporter &BR) const {
1190b57cec5SDimitry Andric   const ObjCInterfaceDecl *InterD = D->getClassInterface();
1200b57cec5SDimitry Andric 
1210b57cec5SDimitry Andric 
1220b57cec5SDimitry Andric   IvarToPropertyMapTy IvarToPropMap;
1230b57cec5SDimitry Andric 
1240b57cec5SDimitry Andric   // Find all properties for this class.
1250b57cec5SDimitry Andric   for (const auto *PD : InterD->instance_properties()) {
1260b57cec5SDimitry Andric     // Find the corresponding IVar.
1270b57cec5SDimitry Andric     const ObjCIvarDecl *ID = findPropertyBackingIvar(PD, InterD,
1280b57cec5SDimitry Andric                                                      Mgr.getASTContext());
1290b57cec5SDimitry Andric 
1300b57cec5SDimitry Andric     if (!ID)
1310b57cec5SDimitry Andric       continue;
1320b57cec5SDimitry Andric 
1330b57cec5SDimitry Andric     // Store the IVar to property mapping.
1340b57cec5SDimitry Andric     IvarToPropMap[ID] = PD;
1350b57cec5SDimitry Andric   }
1360b57cec5SDimitry Andric 
1370b57cec5SDimitry Andric   if (IvarToPropMap.empty())
1380b57cec5SDimitry Andric     return;
1390b57cec5SDimitry Andric 
1400b57cec5SDimitry Andric   for (const auto *M : D->instance_methods()) {
1410b57cec5SDimitry Andric     AnalysisDeclContext *DCtx = Mgr.getAnalysisDeclContext(M);
1420b57cec5SDimitry Andric 
1430b57cec5SDimitry Andric     if ((*ShouldSkipMethod)(M))
1440b57cec5SDimitry Andric       continue;
1450b57cec5SDimitry Andric 
1460b57cec5SDimitry Andric     const Stmt *Body = M->getBody();
147480093f4SDimitry Andric     if (M->isSynthesizedAccessorStub())
148480093f4SDimitry Andric       continue;
1490b57cec5SDimitry Andric     assert(Body);
1500b57cec5SDimitry Andric 
1510b57cec5SDimitry Andric     MethodCrawler MC(IvarToPropMap, M->getCanonicalDecl(), InterD, BR, this,
1520b57cec5SDimitry Andric                      DCtx);
1530b57cec5SDimitry Andric     MC.VisitStmt(Body);
1540b57cec5SDimitry Andric   }
1550b57cec5SDimitry Andric }
1560b57cec5SDimitry Andric 
1570b57cec5SDimitry Andric static bool isAnnotatedToAllowDirectAssignment(const Decl *D) {
1580b57cec5SDimitry Andric   for (const auto *Ann : D->specific_attrs<AnnotateAttr>())
1590b57cec5SDimitry Andric     if (Ann->getAnnotation() ==
1600b57cec5SDimitry Andric         "objc_allow_direct_instance_variable_assignment")
1610b57cec5SDimitry Andric       return true;
1620b57cec5SDimitry Andric   return false;
1630b57cec5SDimitry Andric }
1640b57cec5SDimitry Andric 
1650b57cec5SDimitry Andric void DirectIvarAssignment::MethodCrawler::VisitBinaryOperator(
1660b57cec5SDimitry Andric                                                     const BinaryOperator *BO) {
1670b57cec5SDimitry Andric   if (!BO->isAssignmentOp())
1680b57cec5SDimitry Andric     return;
1690b57cec5SDimitry Andric 
1700b57cec5SDimitry Andric   const ObjCIvarRefExpr *IvarRef =
1710b57cec5SDimitry Andric           dyn_cast<ObjCIvarRefExpr>(BO->getLHS()->IgnoreParenCasts());
1720b57cec5SDimitry Andric 
1730b57cec5SDimitry Andric   if (!IvarRef)
1740b57cec5SDimitry Andric     return;
1750b57cec5SDimitry Andric 
1760b57cec5SDimitry Andric   if (const ObjCIvarDecl *D = IvarRef->getDecl()) {
1770b57cec5SDimitry Andric     IvarToPropertyMapTy::const_iterator I = IvarToPropMap.find(D);
1780b57cec5SDimitry Andric 
1790b57cec5SDimitry Andric     if (I != IvarToPropMap.end()) {
1800b57cec5SDimitry Andric       const ObjCPropertyDecl *PD = I->second;
1810b57cec5SDimitry Andric       // Skip warnings on Ivars, annotated with
1820b57cec5SDimitry Andric       // objc_allow_direct_instance_variable_assignment. This annotation serves
1830b57cec5SDimitry Andric       // as a false positive suppression mechanism for the checker. The
1840b57cec5SDimitry Andric       // annotation is allowed on properties and ivars.
1850b57cec5SDimitry Andric       if (isAnnotatedToAllowDirectAssignment(PD) ||
1860b57cec5SDimitry Andric           isAnnotatedToAllowDirectAssignment(D))
1870b57cec5SDimitry Andric         return;
1880b57cec5SDimitry Andric 
1890b57cec5SDimitry Andric       ObjCMethodDecl *GetterMethod =
1900b57cec5SDimitry Andric           InterfD->getInstanceMethod(PD->getGetterName());
1910b57cec5SDimitry Andric       ObjCMethodDecl *SetterMethod =
1920b57cec5SDimitry Andric           InterfD->getInstanceMethod(PD->getSetterName());
1930b57cec5SDimitry Andric 
1940b57cec5SDimitry Andric       if (SetterMethod && SetterMethod->getCanonicalDecl() == MD)
1950b57cec5SDimitry Andric         return;
1960b57cec5SDimitry Andric 
1970b57cec5SDimitry Andric       if (GetterMethod && GetterMethod->getCanonicalDecl() == MD)
1980b57cec5SDimitry Andric         return;
1990b57cec5SDimitry Andric 
2000b57cec5SDimitry Andric       BR.EmitBasicReport(
2010b57cec5SDimitry Andric           MD, Checker, "Property access", categories::CoreFoundationObjectiveC,
2020b57cec5SDimitry Andric           "Direct assignment to an instance variable backing a property; "
2030b57cec5SDimitry Andric           "use the setter instead",
2040b57cec5SDimitry Andric           PathDiagnosticLocation(IvarRef, BR.getSourceManager(), DCtx));
2050b57cec5SDimitry Andric     }
2060b57cec5SDimitry Andric   }
2070b57cec5SDimitry Andric }
2080b57cec5SDimitry Andric }
2090b57cec5SDimitry Andric 
2100b57cec5SDimitry Andric // Register the checker that checks for direct accesses in functions annotated
2110b57cec5SDimitry Andric // with __attribute__((annotate("objc_no_direct_instance_variable_assignment"))).
2120b57cec5SDimitry Andric static bool AttrFilter(const ObjCMethodDecl *M) {
2130b57cec5SDimitry Andric   for (const auto *Ann : M->specific_attrs<AnnotateAttr>())
2140b57cec5SDimitry Andric     if (Ann->getAnnotation() == "objc_no_direct_instance_variable_assignment")
2150b57cec5SDimitry Andric       return false;
2160b57cec5SDimitry Andric   return true;
2170b57cec5SDimitry Andric }
2180b57cec5SDimitry Andric 
2190b57cec5SDimitry Andric // Register the checker that checks for direct accesses in all functions,
2200b57cec5SDimitry Andric // except for the initialization and copy routines.
2210b57cec5SDimitry Andric void ento::registerDirectIvarAssignment(CheckerManager &mgr) {
2225ffd83dbSDimitry Andric   auto Chk = mgr.registerChecker<DirectIvarAssignment>();
2235ffd83dbSDimitry Andric   if (mgr.getAnalyzerOptions().getCheckerBooleanOption(Chk,
2245ffd83dbSDimitry Andric                                                        "AnnotatedFunctions"))
2255ffd83dbSDimitry Andric     Chk->ShouldSkipMethod = &AttrFilter;
2260b57cec5SDimitry Andric }
2270b57cec5SDimitry Andric 
2285ffd83dbSDimitry Andric bool ento::shouldRegisterDirectIvarAssignment(const CheckerManager &mgr) {
2290b57cec5SDimitry Andric   return true;
2300b57cec5SDimitry Andric }
231