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
23using namespace clang;
24using namespace ento;
25
26namespace {
27class 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
40public:
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
50private:
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.
62REGISTER_SET_WITH_PROGRAMSTATE(CalledSuperDealloc, SymbolRef)
63
64namespace {
65class SuperDeallocBRVisitor final
66    : public BugReporterVisitorImpl<SuperDeallocBRVisitor> {
67
68  SymbolRef ReceiverSymbol;
69  bool Satisfied;
70
71public:
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
87void 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
114void ObjCSuperDeallocChecker::checkPreCall(const CallEvent &Call,
115                                           CheckerContext &C) const {
116  diagnoseCallArguments(Call, C);
117}
118
119void 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
136void 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.
181void 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.
206void 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
222ObjCSuperDeallocChecker::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
230void
231ObjCSuperDeallocChecker::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
241bool
242ObjCSuperDeallocChecker::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
252PathDiagnosticPiece *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
289void 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