Home | History | Annotate | Download | only in Checkers
      1 //==- CheckObjCDealloc.cpp - Check ObjC -dealloc implementation --*- C++ -*-==//
      2 //
      3 //                     The LLVM Compiler Infrastructure
      4 //
      5 // This file is distributed under the University of Illinois Open Source
      6 // License. See LICENSE.TXT for details.
      7 //
      8 //===----------------------------------------------------------------------===//
      9 //
     10 //  This file defines a CheckObjCDealloc, a checker that
     11 //  analyzes an Objective-C class's implementation to determine if it
     12 //  correctly implements -dealloc.
     13 //
     14 //===----------------------------------------------------------------------===//
     15 
     16 #include "ClangSACheckers.h"
     17 #include "clang/AST/Attr.h"
     18 #include "clang/AST/DeclObjC.h"
     19 #include "clang/AST/Expr.h"
     20 #include "clang/AST/ExprObjC.h"
     21 #include "clang/Basic/LangOptions.h"
     22 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
     23 #include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h"
     24 #include "clang/StaticAnalyzer/Core/Checker.h"
     25 #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
     26 #include "llvm/Support/raw_ostream.h"
     27 
     28 using namespace clang;
     29 using namespace ento;
     30 
     31 static bool scan_ivar_release(Stmt *S, ObjCIvarDecl *ID,
     32                               const ObjCPropertyDecl *PD,
     33                               Selector Release,
     34                               IdentifierInfo* SelfII,
     35                               ASTContext &Ctx) {
     36 
     37   // [mMyIvar release]
     38   if (ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(S))
     39     if (ME->getSelector() == Release)
     40       if (ME->getInstanceReceiver())
     41         if (Expr *Receiver = ME->getInstanceReceiver()->IgnoreParenCasts())
     42           if (ObjCIvarRefExpr *E = dyn_cast<ObjCIvarRefExpr>(Receiver))
     43             if (E->getDecl() == ID)
     44               return true;
     45 
     46   // [self setMyIvar:nil];
     47   if (ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(S))
     48     if (ME->getInstanceReceiver())
     49       if (Expr *Receiver = ME->getInstanceReceiver()->IgnoreParenCasts())
     50         if (DeclRefExpr *E = dyn_cast<DeclRefExpr>(Receiver))
     51           if (E->getDecl()->getIdentifier() == SelfII)
     52             if (ME->getMethodDecl() == PD->getSetterMethodDecl() &&
     53                 ME->getNumArgs() == 1 &&
     54                 ME->getArg(0)->isNullPointerConstant(Ctx,
     55                                               Expr::NPC_ValueDependentIsNull))
     56               return true;
     57 
     58   // self.myIvar = nil;
     59   if (BinaryOperator* BO = dyn_cast<BinaryOperator>(S))
     60     if (BO->isAssignmentOp())
     61       if (ObjCPropertyRefExpr *PRE =
     62            dyn_cast<ObjCPropertyRefExpr>(BO->getLHS()->IgnoreParenCasts()))
     63         if (PRE->isExplicitProperty() && PRE->getExplicitProperty() == PD)
     64             if (BO->getRHS()->isNullPointerConstant(Ctx,
     65                                             Expr::NPC_ValueDependentIsNull)) {
     66               // This is only a 'release' if the property kind is not
     67               // 'assign'.
     68               return PD->getSetterKind() != ObjCPropertyDecl::Assign;
     69             }
     70 
     71   // Recurse to children.
     72   for (Stmt *SubStmt : S->children())
     73     if (SubStmt && scan_ivar_release(SubStmt, ID, PD, Release, SelfII, Ctx))
     74       return true;
     75 
     76   return false;
     77 }
     78 
     79 static void checkObjCDealloc(const CheckerBase *Checker,
     80                              const ObjCImplementationDecl *D,
     81                              const LangOptions &LOpts, BugReporter &BR) {
     82 
     83   assert (LOpts.getGC() != LangOptions::GCOnly);
     84 
     85   ASTContext &Ctx = BR.getContext();
     86   const ObjCInterfaceDecl *ID = D->getClassInterface();
     87 
     88   // Does the class contain any ivars that are pointers (or id<...>)?
     89   // If not, skip the check entirely.
     90   // NOTE: This is motivated by PR 2517:
     91   //        http://llvm.org/bugs/show_bug.cgi?id=2517
     92 
     93   bool containsPointerIvar = false;
     94 
     95   for (const auto *Ivar : ID->ivars()) {
     96     QualType T = Ivar->getType();
     97 
     98     if (!T->isObjCObjectPointerType() ||
     99         Ivar->hasAttr<IBOutletAttr>() || // Skip IBOutlets.
    100         Ivar->hasAttr<IBOutletCollectionAttr>()) // Skip IBOutletCollections.
    101       continue;
    102 
    103     containsPointerIvar = true;
    104     break;
    105   }
    106 
    107   if (!containsPointerIvar)
    108     return;
    109 
    110   // Determine if the class subclasses NSObject.
    111   IdentifierInfo* NSObjectII = &Ctx.Idents.get("NSObject");
    112   IdentifierInfo* SenTestCaseII = &Ctx.Idents.get("SenTestCase");
    113 
    114 
    115   for ( ; ID ; ID = ID->getSuperClass()) {
    116     IdentifierInfo *II = ID->getIdentifier();
    117 
    118     if (II == NSObjectII)
    119       break;
    120 
    121     // FIXME: For now, ignore classes that subclass SenTestCase, as these don't
    122     // need to implement -dealloc.  They implement tear down in another way,
    123     // which we should try and catch later.
    124     //  http://llvm.org/bugs/show_bug.cgi?id=3187
    125     if (II == SenTestCaseII)
    126       return;
    127   }
    128 
    129   if (!ID)
    130     return;
    131 
    132   // Get the "dealloc" selector.
    133   IdentifierInfo* II = &Ctx.Idents.get("dealloc");
    134   Selector S = Ctx.Selectors.getSelector(0, &II);
    135   const ObjCMethodDecl *MD = nullptr;
    136 
    137   // Scan the instance methods for "dealloc".
    138   for (const auto *I : D->instance_methods()) {
    139     if (I->getSelector() == S) {
    140       MD = I;
    141       break;
    142     }
    143   }
    144 
    145   PathDiagnosticLocation DLoc =
    146     PathDiagnosticLocation::createBegin(D, BR.getSourceManager());
    147 
    148   if (!MD) { // No dealloc found.
    149 
    150     const char* name = LOpts.getGC() == LangOptions::NonGC
    151                        ? "missing -dealloc"
    152                        : "missing -dealloc (Hybrid MM, non-GC)";
    153 
    154     std::string buf;
    155     llvm::raw_string_ostream os(buf);
    156     os << "Objective-C class '" << *D << "' lacks a 'dealloc' instance method";
    157 
    158     BR.EmitBasicReport(D, Checker, name, categories::CoreFoundationObjectiveC,
    159                        os.str(), DLoc);
    160     return;
    161   }
    162 
    163   // Get the "release" selector.
    164   IdentifierInfo* RII = &Ctx.Idents.get("release");
    165   Selector RS = Ctx.Selectors.getSelector(0, &RII);
    166 
    167   // Get the "self" identifier
    168   IdentifierInfo* SelfII = &Ctx.Idents.get("self");
    169 
    170   // Scan for missing and extra releases of ivars used by implementations
    171   // of synthesized properties
    172   for (const auto *I : D->property_impls()) {
    173     // We can only check the synthesized properties
    174     if (I->getPropertyImplementation() != ObjCPropertyImplDecl::Synthesize)
    175       continue;
    176 
    177     ObjCIvarDecl *ID = I->getPropertyIvarDecl();
    178     if (!ID)
    179       continue;
    180 
    181     QualType T = ID->getType();
    182     if (!T->isObjCObjectPointerType()) // Skip non-pointer ivars
    183       continue;
    184 
    185     const ObjCPropertyDecl *PD = I->getPropertyDecl();
    186     if (!PD)
    187       continue;
    188 
    189     // ivars cannot be set via read-only properties, so we'll skip them
    190     if (PD->isReadOnly())
    191       continue;
    192 
    193     // ivar must be released if and only if the kind of setter was not 'assign'
    194     bool requiresRelease = PD->getSetterKind() != ObjCPropertyDecl::Assign;
    195     if (scan_ivar_release(MD->getBody(), ID, PD, RS, SelfII, Ctx)
    196        != requiresRelease) {
    197       const char *name = nullptr;
    198       std::string buf;
    199       llvm::raw_string_ostream os(buf);
    200 
    201       if (requiresRelease) {
    202         name = LOpts.getGC() == LangOptions::NonGC
    203                ? "missing ivar release (leak)"
    204                : "missing ivar release (Hybrid MM, non-GC)";
    205 
    206         os << "The '" << *ID
    207            << "' instance variable was retained by a synthesized property but "
    208               "wasn't released in 'dealloc'";
    209       } else {
    210         name = LOpts.getGC() == LangOptions::NonGC
    211                ? "extra ivar release (use-after-release)"
    212                : "extra ivar release (Hybrid MM, non-GC)";
    213 
    214         os << "The '" << *ID
    215            << "' instance variable was not retained by a synthesized property "
    216               "but was released in 'dealloc'";
    217       }
    218 
    219       PathDiagnosticLocation SDLoc =
    220         PathDiagnosticLocation::createBegin(I, BR.getSourceManager());
    221 
    222       BR.EmitBasicReport(MD, Checker, name,
    223                          categories::CoreFoundationObjectiveC, os.str(), SDLoc);
    224     }
    225   }
    226 }
    227 
    228 //===----------------------------------------------------------------------===//
    229 // ObjCDeallocChecker
    230 //===----------------------------------------------------------------------===//
    231 
    232 namespace {
    233 class ObjCDeallocChecker : public Checker<
    234                                       check::ASTDecl<ObjCImplementationDecl> > {
    235 public:
    236   void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager& mgr,
    237                     BugReporter &BR) const {
    238     if (mgr.getLangOpts().getGC() == LangOptions::GCOnly)
    239       return;
    240     checkObjCDealloc(this, cast<ObjCImplementationDecl>(D), mgr.getLangOpts(),
    241                      BR);
    242   }
    243 };
    244 }
    245 
    246 void ento::registerObjCDeallocChecker(CheckerManager &mgr) {
    247   mgr.registerChecker<ObjCDeallocChecker>();
    248 }
    249