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