1 //===- ObjCSuperDeallocChecker.cpp - Check correct use of [super dealloc] -===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // This defines ObjCSuperDeallocChecker, a builtin check that warns when 10 // self is used after a call to [super dealloc] in MRR mode. 11 // 12 //===----------------------------------------------------------------------===// 13 14 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" 15 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 16 #include "clang/StaticAnalyzer/Core/Checker.h" 17 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" 18 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 19 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" 20 #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" 21 22 using namespace clang; 23 using namespace ento; 24 25 namespace { 26 class ObjCSuperDeallocChecker 27 : public Checker<check::PostObjCMessage, check::PreObjCMessage, 28 check::PreCall, check::Location> { 29 30 mutable IdentifierInfo *IIdealloc, *IINSObject; 31 mutable Selector SELdealloc; 32 33 std::unique_ptr<BugType> DoubleSuperDeallocBugType; 34 35 void initIdentifierInfoAndSelectors(ASTContext &Ctx) const; 36 37 bool isSuperDeallocMessage(const ObjCMethodCall &M) const; 38 39 public: 40 ObjCSuperDeallocChecker(); 41 void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const; 42 void checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const; 43 44 void checkPreCall(const CallEvent &Call, CheckerContext &C) const; 45 46 void checkLocation(SVal l, bool isLoad, const Stmt *S, 47 CheckerContext &C) const; 48 49 private: 50 51 void diagnoseCallArguments(const CallEvent &CE, CheckerContext &C) const; 52 53 void reportUseAfterDealloc(SymbolRef Sym, StringRef Desc, const Stmt *S, 54 CheckerContext &C) const; 55 }; 56 57 } // End anonymous namespace. 58 59 // Remember whether [super dealloc] has previously been called on the 60 // SymbolRef for the receiver. 61 REGISTER_SET_WITH_PROGRAMSTATE(CalledSuperDealloc, SymbolRef) 62 63 namespace { 64 class SuperDeallocBRVisitor final : public BugReporterVisitor { 65 SymbolRef ReceiverSymbol; 66 bool Satisfied; 67 68 public: 69 SuperDeallocBRVisitor(SymbolRef ReceiverSymbol) 70 : ReceiverSymbol(ReceiverSymbol), Satisfied(false) {} 71 72 PathDiagnosticPieceRef VisitNode(const ExplodedNode *Succ, 73 BugReporterContext &BRC, 74 PathSensitiveBugReport &BR) override; 75 76 void Profile(llvm::FoldingSetNodeID &ID) const override { 77 ID.Add(ReceiverSymbol); 78 } 79 }; 80 } // End anonymous namespace. 81 82 void ObjCSuperDeallocChecker::checkPreObjCMessage(const ObjCMethodCall &M, 83 CheckerContext &C) const { 84 85 ProgramStateRef State = C.getState(); 86 SymbolRef ReceiverSymbol = M.getReceiverSVal().getAsSymbol(); 87 if (!ReceiverSymbol) { 88 diagnoseCallArguments(M, C); 89 return; 90 } 91 92 bool AlreadyCalled = State->contains<CalledSuperDealloc>(ReceiverSymbol); 93 if (!AlreadyCalled) 94 return; 95 96 StringRef Desc; 97 98 if (isSuperDeallocMessage(M)) { 99 Desc = "[super dealloc] should not be called multiple times"; 100 } else { 101 Desc = StringRef(); 102 } 103 104 reportUseAfterDealloc(ReceiverSymbol, Desc, M.getOriginExpr(), C); 105 } 106 107 void ObjCSuperDeallocChecker::checkPreCall(const CallEvent &Call, 108 CheckerContext &C) const { 109 diagnoseCallArguments(Call, C); 110 } 111 112 void ObjCSuperDeallocChecker::checkPostObjCMessage(const ObjCMethodCall &M, 113 CheckerContext &C) const { 114 // Check for [super dealloc] method call. 115 if (!isSuperDeallocMessage(M)) 116 return; 117 118 ProgramStateRef State = C.getState(); 119 SymbolRef ReceiverSymbol = M.getSelfSVal().getAsSymbol(); 120 assert(ReceiverSymbol && "No receiver symbol at call to [super dealloc]?"); 121 122 // We add this transition in checkPostObjCMessage to avoid warning when 123 // we inline a call to [super dealloc] where the inlined call itself 124 // calls [super dealloc]. 125 State = State->add<CalledSuperDealloc>(ReceiverSymbol); 126 C.addTransition(State); 127 } 128 129 void ObjCSuperDeallocChecker::checkLocation(SVal L, bool IsLoad, const Stmt *S, 130 CheckerContext &C) const { 131 SymbolRef BaseSym = L.getLocSymbolInBase(); 132 if (!BaseSym) 133 return; 134 135 ProgramStateRef State = C.getState(); 136 137 if (!State->contains<CalledSuperDealloc>(BaseSym)) 138 return; 139 140 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. 174 void 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. 199 void 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 215 ObjCSuperDeallocChecker::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 223 void 224 ObjCSuperDeallocChecker::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 234 bool 235 ObjCSuperDeallocChecker::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 245 PathDiagnosticPieceRef 246 SuperDeallocBRVisitor::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 283 void ento::registerObjCSuperDeallocChecker(CheckerManager &Mgr) { 284 Mgr.registerChecker<ObjCSuperDeallocChecker>(); 285 } 286 287 bool ento::shouldRegisterObjCSuperDeallocChecker(const LangOptions &LO) { 288 return true; 289 } 290