Home | History | Annotate | Download | only in Checkers
      1 //===- ObjCSuperDeallocChecker.cpp - Check correct use of [super dealloc] -===//
      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 defines ObjCSuperDeallocChecker, a builtin check that warns when
     11 // self is used after a call to [super dealloc] in MRR mode.
     12 //
     13 //===----------------------------------------------------------------------===//
     14 
     15 #include "ClangSACheckers.h"
     16 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
     17 #include "clang/StaticAnalyzer/Core/Checker.h"
     18 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
     19 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
     20 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
     21 #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
     22 
     23 using namespace clang;
     24 using namespace ento;
     25 
     26 namespace {
     27 class ObjCSuperDeallocChecker
     28     : public Checker<check::PostObjCMessage, check::PreObjCMessage,
     29                      check::PreCall, check::Location> {
     30 
     31   mutable IdentifierInfo *IIdealloc, *IINSObject;
     32   mutable Selector SELdealloc;
     33 
     34   std::unique_ptr<BugType> DoubleSuperDeallocBugType;
     35 
     36   void initIdentifierInfoAndSelectors(ASTContext &Ctx) const;
     37 
     38   bool isSuperDeallocMessage(const ObjCMethodCall &M) const;
     39 
     40 public:
     41   ObjCSuperDeallocChecker();
     42   void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;
     43   void checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;
     44 
     45   void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
     46 
     47   void checkLocation(SVal l, bool isLoad, const Stmt *S,
     48                      CheckerContext &C) const;
     49 
     50 private:
     51 
     52   void diagnoseCallArguments(const CallEvent &CE, CheckerContext &C) const;
     53 
     54   void reportUseAfterDealloc(SymbolRef Sym, StringRef Desc, const Stmt *S,
     55                              CheckerContext &C) const;
     56 };
     57 
     58 } // End anonymous namespace.
     59 
     60 // Remember whether [super dealloc] has previously been called on the
     61 // SymbolRef for the receiver.
     62 REGISTER_SET_WITH_PROGRAMSTATE(CalledSuperDealloc, SymbolRef)
     63 
     64 namespace {
     65 class SuperDeallocBRVisitor final
     66     : public BugReporterVisitorImpl<SuperDeallocBRVisitor> {
     67 
     68   SymbolRef ReceiverSymbol;
     69   bool Satisfied;
     70 
     71 public:
     72   SuperDeallocBRVisitor(SymbolRef ReceiverSymbol)
     73       : ReceiverSymbol(ReceiverSymbol),
     74         Satisfied(false) {}
     75 
     76   PathDiagnosticPiece *VisitNode(const ExplodedNode *Succ,
     77                                  const ExplodedNode *Pred,
     78                                  BugReporterContext &BRC,
     79                                  BugReport &BR) override;
     80 
     81   void Profile(llvm::FoldingSetNodeID &ID) const override {
     82     ID.Add(ReceiverSymbol);
     83   }
     84 };
     85 } // End anonymous namespace.
     86 
     87 void ObjCSuperDeallocChecker::checkPreObjCMessage(const ObjCMethodCall &M,
     88                                                   CheckerContext &C) const {
     89 
     90   ProgramStateRef State = C.getState();
     91   SymbolRef ReceiverSymbol = M.getReceiverSVal().getAsSymbol();
     92   if (!ReceiverSymbol) {
     93     diagnoseCallArguments(M, C);
     94     return;
     95   }
     96 
     97   bool AlreadyCalled = State->contains<CalledSuperDealloc>(ReceiverSymbol);
     98   if (!AlreadyCalled)
     99     return;
    100 
    101   StringRef Desc;
    102 
    103   if (isSuperDeallocMessage(M)) {
    104     Desc = "[super dealloc] should not be called multiple times";
    105   } else {
    106     Desc = StringRef();
    107   }
    108 
    109   reportUseAfterDealloc(ReceiverSymbol, Desc, M.getOriginExpr(), C);
    110 
    111   return;
    112 }
    113 
    114 void ObjCSuperDeallocChecker::checkPreCall(const CallEvent &Call,
    115                                            CheckerContext &C) const {
    116   diagnoseCallArguments(Call, C);
    117 }
    118 
    119 void ObjCSuperDeallocChecker::checkPostObjCMessage(const ObjCMethodCall &M,
    120                                                    CheckerContext &C) const {
    121   // Check for [super dealloc] method call.
    122   if (!isSuperDeallocMessage(M))
    123     return;
    124 
    125   ProgramStateRef State = C.getState();
    126   SymbolRef ReceiverSymbol = M.getSelfSVal().getAsSymbol();
    127   assert(ReceiverSymbol && "No receiver symbol at call to [super dealloc]?");
    128 
    129   // We add this transition in checkPostObjCMessage to avoid warning when
    130   // we inline a call to [super dealloc] where the inlined call itself
    131   // calls [super dealloc].
    132   State = State->add<CalledSuperDealloc>(ReceiverSymbol);
    133   C.addTransition(State);
    134 }
    135 
    136 void ObjCSuperDeallocChecker::checkLocation(SVal L, bool IsLoad, const Stmt *S,
    137                                   CheckerContext &C) const {
    138   SymbolRef BaseSym = L.getLocSymbolInBase();
    139   if (!BaseSym)
    140     return;
    141 
    142   ProgramStateRef State = C.getState();
    143 
    144   if (!State->contains<CalledSuperDealloc>(BaseSym))
    145     return;
    146 
    147   const MemRegion *R = L.getAsRegion();
    148   if (!R)
    149     return;
    150 
    151   // Climb the super regions to find the base symbol while recording
    152   // the second-to-last region for error reporting.
    153   const MemRegion *PriorSubRegion = nullptr;
    154   while (const SubRegion *SR = dyn_cast<SubRegion>(R)) {
    155     if (const SymbolicRegion *SymR = dyn_cast<SymbolicRegion>(SR)) {
    156       BaseSym = SymR->getSymbol();
    157       break;
    158     } else {
    159       R = SR->getSuperRegion();
    160       PriorSubRegion = SR;
    161     }
    162   }
    163 
    164   StringRef Desc = StringRef();
    165   auto *IvarRegion = dyn_cast_or_null<ObjCIvarRegion>(PriorSubRegion);
    166 
    167   std::string Buf;
    168   llvm::raw_string_ostream OS(Buf);
    169   if (IvarRegion) {
    170     OS << "Use of instance variable '" << *IvarRegion->getDecl() <<
    171           "' after 'self' has been deallocated";
    172     Desc = OS.str();
    173   }
    174 
    175   reportUseAfterDealloc(BaseSym, Desc, S, C);
    176 }
    177 
    178 /// Report a use-after-dealloc on Sym. If not empty,
    179 /// Desc will be used to describe the error; otherwise,
    180 /// a default warning will be used.
    181 void ObjCSuperDeallocChecker::reportUseAfterDealloc(SymbolRef Sym,
    182                                                     StringRef Desc,
    183                                                     const Stmt *S,
    184                                                     CheckerContext &C) const {
    185   // We have a use of self after free.
    186   // This likely causes a crash, so stop exploring the
    187   // path by generating a sink.
    188   ExplodedNode *ErrNode = C.generateErrorNode();
    189   // If we've already reached this node on another path, return.
    190   if (!ErrNode)
    191     return;
    192 
    193   if (Desc.empty())
    194     Desc = "use of 'self' after it has been deallocated";
    195 
    196   // Generate the report.
    197   std::unique_ptr<BugReport> BR(
    198       new BugReport(*DoubleSuperDeallocBugType, Desc, ErrNode));
    199   BR->addRange(S->getSourceRange());
    200   BR->addVisitor(llvm::make_unique<SuperDeallocBRVisitor>(Sym));
    201   C.emitReport(std::move(BR));
    202 }
    203 
    204 /// Diagnose if any of the arguments to CE have already been
    205 /// dealloc'd.
    206 void ObjCSuperDeallocChecker::diagnoseCallArguments(const CallEvent &CE,
    207                                                     CheckerContext &C) const {
    208   ProgramStateRef State = C.getState();
    209   unsigned ArgCount = CE.getNumArgs();
    210   for (unsigned I = 0; I < ArgCount; I++) {
    211     SymbolRef Sym = CE.getArgSVal(I).getAsSymbol();
    212     if (!Sym)
    213       continue;
    214 
    215     if (State->contains<CalledSuperDealloc>(Sym)) {
    216       reportUseAfterDealloc(Sym, StringRef(), CE.getArgExpr(I), C);
    217       return;
    218     }
    219   }
    220 }
    221 
    222 ObjCSuperDeallocChecker::ObjCSuperDeallocChecker()
    223     : IIdealloc(nullptr), IINSObject(nullptr) {
    224 
    225   DoubleSuperDeallocBugType.reset(
    226       new BugType(this, "[super dealloc] should not be called more than once",
    227                   categories::CoreFoundationObjectiveC));
    228 }
    229 
    230 void
    231 ObjCSuperDeallocChecker::initIdentifierInfoAndSelectors(ASTContext &Ctx) const {
    232   if (IIdealloc)
    233     return;
    234 
    235   IIdealloc = &Ctx.Idents.get("dealloc");
    236   IINSObject = &Ctx.Idents.get("NSObject");
    237 
    238   SELdealloc = Ctx.Selectors.getSelector(0, &IIdealloc);
    239 }
    240 
    241 bool
    242 ObjCSuperDeallocChecker::isSuperDeallocMessage(const ObjCMethodCall &M) const {
    243   if (M.getOriginExpr()->getReceiverKind() != ObjCMessageExpr::SuperInstance)
    244     return false;
    245 
    246   ASTContext &Ctx = M.getState()->getStateManager().getContext();
    247   initIdentifierInfoAndSelectors(Ctx);
    248 
    249   return M.getSelector() == SELdealloc;
    250 }
    251 
    252 PathDiagnosticPiece *SuperDeallocBRVisitor::VisitNode(const ExplodedNode *Succ,
    253                                                       const ExplodedNode *Pred,
    254                                                       BugReporterContext &BRC,
    255                                                       BugReport &BR) {
    256   if (Satisfied)
    257     return nullptr;
    258 
    259   ProgramStateRef State = Succ->getState();
    260 
    261   bool CalledNow =
    262       Succ->getState()->contains<CalledSuperDealloc>(ReceiverSymbol);
    263   bool CalledBefore =
    264       Pred->getState()->contains<CalledSuperDealloc>(ReceiverSymbol);
    265 
    266   // Is Succ the node on which the analyzer noted that [super dealloc] was
    267   // called on ReceiverSymbol?
    268   if (CalledNow && !CalledBefore) {
    269     Satisfied = true;
    270 
    271     ProgramPoint P = Succ->getLocation();
    272     PathDiagnosticLocation L =
    273         PathDiagnosticLocation::create(P, BRC.getSourceManager());
    274 
    275     if (!L.isValid() || !L.asLocation().isValid())
    276       return nullptr;
    277 
    278     return new PathDiagnosticEventPiece(
    279         L, "[super dealloc] called here");
    280   }
    281 
    282   return nullptr;
    283 }
    284 
    285 //===----------------------------------------------------------------------===//
    286 // Checker Registration.
    287 //===----------------------------------------------------------------------===//
    288 
    289 void ento::registerObjCSuperDeallocChecker(CheckerManager &Mgr) {
    290   const LangOptions &LangOpts = Mgr.getLangOpts();
    291   if (LangOpts.getGC() == LangOptions::GCOnly || LangOpts.ObjCAutoRefCount)
    292     return;
    293   Mgr.registerChecker<ObjCSuperDeallocChecker>();
    294 }
    295