194742Sobrien//===- ObjCSuperDeallocChecker.cpp - Check correct use of [super dealloc] -===//
294742Sobrien//
3146890Speter// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4146890Speter// See https://llvm.org/LICENSE.txt for license information.
5179626Speter// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6146890Speter//
7146890Speter//===----------------------------------------------------------------------===//
8146890Speter//
9146890Speter// This defines ObjCSuperDeallocChecker, a builtin check that warns when
10146890Speter// self is used after a call to [super dealloc] in MRR mode.
11146890Speter//
12146890Speter//===----------------------------------------------------------------------===//
13146890Speter
14146890Speter#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
15146890Speter#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
16146890Speter#include "clang/StaticAnalyzer/Core/Checker.h"
17146890Speter#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
18146890Speter#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
19146890Speter#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
20146890Speter#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
2194742Sobrien
2295253Sruusing namespace clang;
23159014Sjmgusing namespace ento;
2494742Sobrien
2596991Srwatsonnamespace {
2696991Srwatsonclass ObjCSuperDeallocChecker
2796991Srwatson    : public Checker<check::PostObjCMessage, check::PreObjCMessage,
28102773Srwatson                     check::PreCall, check::Location> {
29102773Srwatson
30156279Srwatson  mutable IdentifierInfo *IIdealloc, *IINSObject;
31156279Srwatson  mutable Selector SELdealloc;
3294917Simp
33126445Sobrien  std::unique_ptr<BugType> DoubleSuperDeallocBugType;
3494917Simp
35146933Simp  void initIdentifierInfoAndSelectors(ASTContext &Ctx) const;
3694847Sjhb
3794847Sjhb  bool isSuperDeallocMessage(const ObjCMethodCall &M) const;
3894847Sjhb
3994915Skenpublic:
4099607Smjacob  ObjCSuperDeallocChecker();
4194915Sken  void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;
4294915Sken  void checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const;
4394915Sken
4494915Sken  void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
4594915Sken
4694915Sken  void checkLocation(SVal l, bool isLoad, const Stmt *S,
4794915Sken                     CheckerContext &C) const;
4894915Sken
49169922Sjfvprivate:
5097611Sbillf
5194918Sgshapiro  void diagnoseCallArguments(const CallEvent &CE, CheckerContext &C) const;
5294918Sgshapiro
5394918Sgshapiro  void reportUseAfterDealloc(SymbolRef Sym, StringRef Desc, const Stmt *S,
5494918Sgshapiro                             CheckerContext &C) const;
5594918Sgshapiro};
56106187Sdes
57106187Sdes} // End anonymous namespace.
5895455Sdes
5998750Sdes// Remember whether [super dealloc] has previously been called on the
60205686Sdes// SymbolRef for the receiver.
61205686SdesREGISTER_SET_WITH_PROGRAMSTATE(CalledSuperDealloc, SymbolRef)
62205686Sdes
6396268Sgadnamespace {
6496268Sgadclass SuperDeallocBRVisitor final : public BugReporterVisitor {
65116233Sgad  SymbolRef ReceiverSymbol;
6696332Speter  bool Satisfied;
6796332Speter
6896332Speterpublic:
69100314Sru  SuperDeallocBRVisitor(SymbolRef ReceiverSymbol)
70146921Sru      : ReceiverSymbol(ReceiverSymbol), Satisfied(false) {}
71146921Sru
7297611Sbillf  PathDiagnosticPieceRef VisitNode(const ExplodedNode *Succ,
73190171Srnoland                                   BugReporterContext &BRC,
74115825Sfanf                                   PathSensitiveBugReport &BR) override;
75126445Sobrien
76117645Sdwmalone  void Profile(llvm::FoldingSetNodeID &ID) const override {
77118204Sbp    ID.Add(ReceiverSymbol);
78118204Sbp  }
79118204Sbp};
80118204Sbp} // End anonymous namespace.
81127337Smlaier
82126445Sobrienvoid ObjCSuperDeallocChecker::checkPreObjCMessage(const ObjCMethodCall &M,
83146837Sobrien                                                  CheckerContext &C) const {
84146837Sobrien
85146837Sobrien  ProgramStateRef State = C.getState();
86146837Sobrien  SymbolRef ReceiverSymbol = M.getReceiverSVal().getAsSymbol();
87133182Spjd  if (!ReceiverSymbol) {
88148779Spjd    diagnoseCallArguments(M, C);
89133182Spjd    return;
90133182Spjd  }
91133182Spjd
92133182Spjd  bool AlreadyCalled = State->contains<CalledSuperDealloc>(ReceiverSymbol);
93133841Spjd  if (!AlreadyCalled)
94143521Spjd    return;
95133182Spjd
96148779Spjd  StringRef Desc;
97133182Spjd
98168419Spjd  if (isSuperDeallocMessage(M)) {
99132311Salfred    Desc = "[super dealloc] should not be called multiple times";
100132311Salfred  } else {
101132311Salfred    Desc = StringRef();
102132268Salfred  }
103158323Srodrigc
104158323Srodrigc  reportUseAfterDealloc(ReceiverSymbol, Desc, M.getOriginExpr(), C);
105195405Sflz}
106146960Simp
107148772Scpercivavoid ObjCSuperDeallocChecker::checkPreCall(const CallEvent &Call,
108148871Scperciva                                           CheckerContext &C) const {
109161748Scperciva  diagnoseCallArguments(Call, C);
110162226Ssimon}
111149464Semax
112149464Semaxvoid ObjCSuperDeallocChecker::checkPostObjCMessage(const ObjCMethodCall &M,
113149464Semax                                                   CheckerContext &C) const {
114149464Semax  // Check for [super dealloc] method call.
115149464Semax  if (!isSuperDeallocMessage(M))
116151618Sceri    return;
117179534Srafan
118200194Scperciva  ProgramStateRef State = C.getState();
119200194Scperciva  SymbolRef ReceiverSymbol = M.getSelfSVal().getAsSymbol();
120204061Sedwin  assert(ReceiverSymbol && "No receiver symbol at call to [super dealloc]?");
121204061Sedwin
122204061Sedwin  // We add this transition in checkPostObjCMessage to avoid warning when
123204061Sedwin  // we inline a call to [super dealloc] where the inlined call itself
124204061Sedwin  // calls [super dealloc].
125204061Sedwin  State = State->add<CalledSuperDealloc>(ReceiverSymbol);
126206396Scperciva  C.addTransition(State);
127206396Scperciva}
128115822Sdougb
129115822Sdougbvoid ObjCSuperDeallocChecker::checkLocation(SVal L, bool IsLoad, const Stmt *S,
130115822Sdougb                                  CheckerContext &C) const {
131115822Sdougb  SymbolRef BaseSym = L.getLocSymbolInBase();
132115822Sdougb  if (!BaseSym)
133115822Sdougb    return;
134115822Sdougb
135146629Sschweikh  ProgramStateRef State = C.getState();
136146629Sschweikh
137146629Sschweikh  if (!State->contains<CalledSuperDealloc>(BaseSym))
138115822Sdougb    return;
139115822Sdougb
140146586Sschweikh  const MemRegion *R = L.getAsRegion();
141  if (!R)
142    return;
143
144  // Climb the super regions to find the base symbol while recording
145  // the second-to-last region for error reporting.
146  const MemRegion *PriorSubRegion = nullptr;
147  while (const SubRegion *SR = dyn_cast<SubRegion>(R)) {
148    if (const SymbolicRegion *SymR = dyn_cast<SymbolicRegion>(SR)) {
149      BaseSym = SymR->getSymbol();
150      break;
151    } else {
152      R = SR->getSuperRegion();
153      PriorSubRegion = SR;
154    }
155  }
156
157  StringRef Desc = StringRef();
158  auto *IvarRegion = dyn_cast_or_null<ObjCIvarRegion>(PriorSubRegion);
159
160  std::string Buf;
161  llvm::raw_string_ostream OS(Buf);
162  if (IvarRegion) {
163    OS << "Use of instance variable '" << *IvarRegion->getDecl() <<
164          "' after 'self' has been deallocated";
165    Desc = OS.str();
166  }
167
168  reportUseAfterDealloc(BaseSym, Desc, S, C);
169}
170
171/// Report a use-after-dealloc on Sym. If not empty,
172/// Desc will be used to describe the error; otherwise,
173/// a default warning will be used.
174void ObjCSuperDeallocChecker::reportUseAfterDealloc(SymbolRef Sym,
175                                                    StringRef Desc,
176                                                    const Stmt *S,
177                                                    CheckerContext &C) const {
178  // We have a use of self after free.
179  // This likely causes a crash, so stop exploring the
180  // path by generating a sink.
181  ExplodedNode *ErrNode = C.generateErrorNode();
182  // If we've already reached this node on another path, return.
183  if (!ErrNode)
184    return;
185
186  if (Desc.empty())
187    Desc = "Use of 'self' after it has been deallocated";
188
189  // Generate the report.
190  auto BR = std::make_unique<PathSensitiveBugReport>(*DoubleSuperDeallocBugType,
191                                                     Desc, ErrNode);
192  BR->addRange(S->getSourceRange());
193  BR->addVisitor(std::make_unique<SuperDeallocBRVisitor>(Sym));
194  C.emitReport(std::move(BR));
195}
196
197/// Diagnose if any of the arguments to CE have already been
198/// dealloc'd.
199void ObjCSuperDeallocChecker::diagnoseCallArguments(const CallEvent &CE,
200                                                    CheckerContext &C) const {
201  ProgramStateRef State = C.getState();
202  unsigned ArgCount = CE.getNumArgs();
203  for (unsigned I = 0; I < ArgCount; I++) {
204    SymbolRef Sym = CE.getArgSVal(I).getAsSymbol();
205    if (!Sym)
206      continue;
207
208    if (State->contains<CalledSuperDealloc>(Sym)) {
209      reportUseAfterDealloc(Sym, StringRef(), CE.getArgExpr(I), C);
210      return;
211    }
212  }
213}
214
215ObjCSuperDeallocChecker::ObjCSuperDeallocChecker()
216    : IIdealloc(nullptr), IINSObject(nullptr) {
217
218  DoubleSuperDeallocBugType.reset(
219      new BugType(this, "[super dealloc] should not be called more than once",
220                  categories::CoreFoundationObjectiveC));
221}
222
223void
224ObjCSuperDeallocChecker::initIdentifierInfoAndSelectors(ASTContext &Ctx) const {
225  if (IIdealloc)
226    return;
227
228  IIdealloc = &Ctx.Idents.get("dealloc");
229  IINSObject = &Ctx.Idents.get("NSObject");
230
231  SELdealloc = Ctx.Selectors.getSelector(0, &IIdealloc);
232}
233
234bool
235ObjCSuperDeallocChecker::isSuperDeallocMessage(const ObjCMethodCall &M) const {
236  if (M.getOriginExpr()->getReceiverKind() != ObjCMessageExpr::SuperInstance)
237    return false;
238
239  ASTContext &Ctx = M.getState()->getStateManager().getContext();
240  initIdentifierInfoAndSelectors(Ctx);
241
242  return M.getSelector() == SELdealloc;
243}
244
245PathDiagnosticPieceRef
246SuperDeallocBRVisitor::VisitNode(const ExplodedNode *Succ,
247                                 BugReporterContext &BRC,
248                                 PathSensitiveBugReport &) {
249  if (Satisfied)
250    return nullptr;
251
252  ProgramStateRef State = Succ->getState();
253
254  bool CalledNow =
255      Succ->getState()->contains<CalledSuperDealloc>(ReceiverSymbol);
256  bool CalledBefore =
257      Succ->getFirstPred()->getState()->contains<CalledSuperDealloc>(
258          ReceiverSymbol);
259
260  // Is Succ the node on which the analyzer noted that [super dealloc] was
261  // called on ReceiverSymbol?
262  if (CalledNow && !CalledBefore) {
263    Satisfied = true;
264
265    ProgramPoint P = Succ->getLocation();
266    PathDiagnosticLocation L =
267        PathDiagnosticLocation::create(P, BRC.getSourceManager());
268
269    if (!L.isValid() || !L.asLocation().isValid())
270      return nullptr;
271
272    return std::make_shared<PathDiagnosticEventPiece>(
273        L, "[super dealloc] called here");
274  }
275
276  return nullptr;
277}
278
279//===----------------------------------------------------------------------===//
280// Checker Registration.
281//===----------------------------------------------------------------------===//
282
283void ento::registerObjCSuperDeallocChecker(CheckerManager &Mgr) {
284  Mgr.registerChecker<ObjCSuperDeallocChecker>();
285}
286
287bool ento::shouldRegisterObjCSuperDeallocChecker(const LangOptions &LO) {
288  return true;
289}
290