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 const LocationContext *LC = C.getLocationContext(); 120 SymbolRef SelfSymbol = State->getSelfSVal(LC).getAsSymbol(); 121 assert(SelfSymbol && "No receiver symbol at call to [super dealloc]?"); 122 123 // We add this transition in checkPostObjCMessage to avoid warning when 124 // we inline a call to [super dealloc] where the inlined call itself 125 // calls [super dealloc]. 126 State = State->add<CalledSuperDealloc>(SelfSymbol); 127 C.addTransition(State); 128 } 129 130 void ObjCSuperDeallocChecker::checkLocation(SVal L, bool IsLoad, const Stmt *S, 131 CheckerContext &C) const { 132 SymbolRef BaseSym = L.getLocSymbolInBase(); 133 if (!BaseSym) 134 return; 135 136 ProgramStateRef State = C.getState(); 137 138 if (!State->contains<CalledSuperDealloc>(BaseSym)) 139 return; 140 141 const MemRegion *R = L.getAsRegion(); 142 if (!R) 143 return; 144 145 // Climb the super regions to find the base symbol while recording 146 // the second-to-last region for error reporting. 147 const MemRegion *PriorSubRegion = nullptr; 148 while (const SubRegion *SR = dyn_cast<SubRegion>(R)) { 149 if (const SymbolicRegion *SymR = dyn_cast<SymbolicRegion>(SR)) { 150 BaseSym = SymR->getSymbol(); 151 break; 152 } else { 153 R = SR->getSuperRegion(); 154 PriorSubRegion = SR; 155 } 156 } 157 158 StringRef Desc = StringRef(); 159 auto *IvarRegion = dyn_cast_or_null<ObjCIvarRegion>(PriorSubRegion); 160 161 std::string Buf; 162 llvm::raw_string_ostream OS(Buf); 163 if (IvarRegion) { 164 OS << "Use of instance variable '" << *IvarRegion->getDecl() << 165 "' after 'self' has been deallocated"; 166 Desc = OS.str(); 167 } 168 169 reportUseAfterDealloc(BaseSym, Desc, S, C); 170 } 171 172 /// Report a use-after-dealloc on Sym. If not empty, 173 /// Desc will be used to describe the error; otherwise, 174 /// a default warning will be used. 175 void ObjCSuperDeallocChecker::reportUseAfterDealloc(SymbolRef Sym, 176 StringRef Desc, 177 const Stmt *S, 178 CheckerContext &C) const { 179 // We have a use of self after free. 180 // This likely causes a crash, so stop exploring the 181 // path by generating a sink. 182 ExplodedNode *ErrNode = C.generateErrorNode(); 183 // If we've already reached this node on another path, return. 184 if (!ErrNode) 185 return; 186 187 if (Desc.empty()) 188 Desc = "Use of 'self' after it has been deallocated"; 189 190 // Generate the report. 191 auto BR = std::make_unique<PathSensitiveBugReport>(*DoubleSuperDeallocBugType, 192 Desc, ErrNode); 193 BR->addRange(S->getSourceRange()); 194 BR->addVisitor(std::make_unique<SuperDeallocBRVisitor>(Sym)); 195 C.emitReport(std::move(BR)); 196 } 197 198 /// Diagnose if any of the arguments to CE have already been 199 /// dealloc'd. 200 void ObjCSuperDeallocChecker::diagnoseCallArguments(const CallEvent &CE, 201 CheckerContext &C) const { 202 ProgramStateRef State = C.getState(); 203 unsigned ArgCount = CE.getNumArgs(); 204 for (unsigned I = 0; I < ArgCount; I++) { 205 SymbolRef Sym = CE.getArgSVal(I).getAsSymbol(); 206 if (!Sym) 207 continue; 208 209 if (State->contains<CalledSuperDealloc>(Sym)) { 210 reportUseAfterDealloc(Sym, StringRef(), CE.getArgExpr(I), C); 211 return; 212 } 213 } 214 } 215 216 ObjCSuperDeallocChecker::ObjCSuperDeallocChecker() 217 : IIdealloc(nullptr), IINSObject(nullptr) { 218 219 DoubleSuperDeallocBugType.reset( 220 new BugType(this, "[super dealloc] should not be called more than once", 221 categories::CoreFoundationObjectiveC)); 222 } 223 224 void 225 ObjCSuperDeallocChecker::initIdentifierInfoAndSelectors(ASTContext &Ctx) const { 226 if (IIdealloc) 227 return; 228 229 IIdealloc = &Ctx.Idents.get("dealloc"); 230 IINSObject = &Ctx.Idents.get("NSObject"); 231 232 SELdealloc = Ctx.Selectors.getSelector(0, &IIdealloc); 233 } 234 235 bool 236 ObjCSuperDeallocChecker::isSuperDeallocMessage(const ObjCMethodCall &M) const { 237 if (M.getOriginExpr()->getReceiverKind() != ObjCMessageExpr::SuperInstance) 238 return false; 239 240 ASTContext &Ctx = M.getState()->getStateManager().getContext(); 241 initIdentifierInfoAndSelectors(Ctx); 242 243 return M.getSelector() == SELdealloc; 244 } 245 246 PathDiagnosticPieceRef 247 SuperDeallocBRVisitor::VisitNode(const ExplodedNode *Succ, 248 BugReporterContext &BRC, 249 PathSensitiveBugReport &) { 250 if (Satisfied) 251 return nullptr; 252 253 ProgramStateRef State = Succ->getState(); 254 255 bool CalledNow = 256 Succ->getState()->contains<CalledSuperDealloc>(ReceiverSymbol); 257 bool CalledBefore = 258 Succ->getFirstPred()->getState()->contains<CalledSuperDealloc>( 259 ReceiverSymbol); 260 261 // Is Succ the node on which the analyzer noted that [super dealloc] was 262 // called on ReceiverSymbol? 263 if (CalledNow && !CalledBefore) { 264 Satisfied = true; 265 266 ProgramPoint P = Succ->getLocation(); 267 PathDiagnosticLocation L = 268 PathDiagnosticLocation::create(P, BRC.getSourceManager()); 269 270 if (!L.isValid() || !L.asLocation().isValid()) 271 return nullptr; 272 273 return std::make_shared<PathDiagnosticEventPiece>( 274 L, "[super dealloc] called here"); 275 } 276 277 return nullptr; 278 } 279 280 //===----------------------------------------------------------------------===// 281 // Checker Registration. 282 //===----------------------------------------------------------------------===// 283 284 void ento::registerObjCSuperDeallocChecker(CheckerManager &Mgr) { 285 Mgr.registerChecker<ObjCSuperDeallocChecker>(); 286 } 287 288 bool ento::shouldRegisterObjCSuperDeallocChecker(const CheckerManager &mgr) { 289 return true; 290 } 291