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