diff options
Diffstat (limited to 'clang/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp')
| -rw-r--r-- | clang/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp | 235 | 
1 files changed, 235 insertions, 0 deletions
| diff --git a/clang/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp b/clang/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp new file mode 100644 index 000000000000..0058f3d3881f --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp @@ -0,0 +1,235 @@ +//=- 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).find("init") != StringRef::npos || +         M->getSelector().getNameForSlot(0).find("Init") != StringRef::npos; +} + +class DirectIvarAssignment : +  public Checker<check::ASTDecl<ObjCImplementationDecl> > { + +  typedef llvm::DenseMap<const ObjCIvarDecl*, +                         const ObjCPropertyDecl*> IvarToPropertyMapTy; + +  /// A helper class, which walks the AST and locates all assignments to ivars +  /// in the given function. +  class MethodCrawler : public ConstStmtVisitor<MethodCrawler> { +    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<ObjCInterfaceDecl*>(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(); +    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<AnnotateAttr>()) +    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<ObjCIvarRefExpr>(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<AnnotateAttr>()) +    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) { +  mgr.registerChecker<DirectIvarAssignment>(); +} + +bool ento::shouldRegisterDirectIvarAssignment(const LangOptions &LO) { +  return true; +} + +void ento::registerDirectIvarAssignmentForAnnotatedFunctions( +    CheckerManager &mgr) { +  mgr.getChecker<DirectIvarAssignment>()->ShouldSkipMethod = &AttrFilter; +} + +bool ento::shouldRegisterDirectIvarAssignmentForAnnotatedFunctions( +                                                        const LangOptions &LO) { +  return true; +} | 
