SimpleStreamChecker.cpp revision 243791
150479Speter//===-- SimpleStreamChecker.cpp -----------------------------------------*- C++ -*--// 26059Samurai// 36059Samurai// The LLVM Compiler Infrastructure 480029Sobrien// 546686Sbrian// This file is distributed under the University of Illinois Open Source 646686Sbrian// License. See LICENSE.TXT for details. 781634Sbrian// 881634Sbrian//===----------------------------------------------------------------------===// 981634Sbrian// 1081634Sbrian// Defines a checker for proper use of fopen/fclose APIs. 11134836Smarcel// - If a file has been closed with fclose, it should not be accessed again. 1293418Sbrian// Accessing a closed file results in undefined behavior. 1393418Sbrian// - If a file was opened with fopen, it must be closed with fclose before 14136910Sru// the execution ends. Failing to do so results in a resource leak. 15136910Sru// 16136910Sru//===----------------------------------------------------------------------===// 17136910Sru 18136910Sru#include "ClangSACheckers.h" 19136910Sru#include "clang/StaticAnalyzer/Core/Checker.h" 20136910Sru#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 21136910Sru#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" 2293418Sbrian#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 2393418Sbrian 2464802Sbrianusing namespace clang; 25116737Srwatsonusing namespace ento; 2664802Sbrian 27116737Srwatsonnamespace { 2840530Sbriantypedef llvm::SmallVector<SymbolRef, 2> SymbolVector; 2964802Sbrian 3029083Sbrianstruct StreamState { 3181534Sbrianprivate: 3236285Sbrian enum Kind { Opened, Closed } K; 3380029Sobrien StreamState(Kind InK) : K(InK) { } 3480029Sobrien 3580029Sobrienpublic: 3681534Sbrian bool isOpened() const { return K == Opened; } 3781534Sbrian bool isClosed() const { return K == Closed; } 3881534Sbrian 3981534Sbrian static StreamState getOpened() { return StreamState(Opened); } 4081534Sbrian static StreamState getClosed() { return StreamState(Closed); } 4193448Sru 4232860Sbrian bool operator==(const StreamState &X) const { 4374687Sbrian return K == X.K; 4474690Sbrian } 4574687Sbrian void Profile(llvm::FoldingSetNodeID &ID) const { 4674687Sbrian ID.AddInteger(K); 4751526Sbrian } 4851526Sbrian}; 4951526Sbrian 5051526Sbrianclass SimpleStreamChecker : public Checker<check::PostCall, 5158071Sbrian check::PreCall, 5258071Sbrian check::DeadSymbols, 5358071Sbrian check::Bind, 5458071Sbrian check::RegionChanges> { 5550264Sbrian 5650191Sbrian mutable IdentifierInfo *IIfopen, *IIfclose; 5731343Sbrian 5851075Sbrian OwningPtr<BugType> DoubleCloseBugType; 5937191Sbrian OwningPtr<BugType> LeakBugType; 6037191Sbrian 6131343Sbrian void initIdentifierInfo(ASTContext &Ctx) const; 6231343Sbrian 6393418Sbrian void reportDoubleClose(SymbolRef FileDescSym, 6465862Sbrian const CallEvent &Call, 6565862Sbrian CheckerContext &C) const; 6665862Sbrian 6765862Sbrian void reportLeaks(SymbolVector LeakedStreams, 6865862Sbrian CheckerContext &C, 6964802Sbrian ExplodedNode *ErrNode) const; 7064802Sbrian 7164802Sbrian bool guaranteedNotToCloseFile(const CallEvent &Call) const; 7264802Sbrian 7364802Sbrianpublic: 7464802Sbrian SimpleStreamChecker(); 75117981Smarkm 7693418Sbrian /// Process fopen. 7793418Sbrian void checkPostCall(const CallEvent &Call, CheckerContext &C) const; 7867910Sbrian /// Process fclose. 7957451Smarkm void checkPreCall(const CallEvent &Call, CheckerContext &C) const; 8057451Smarkm 8129840Sbrian void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; 8229840Sbrian 8343313Sbrian /// Deal with symbol escape as a byproduct of a bind. 8443313Sbrian void checkBind(SVal location, SVal val, const Stmt*S, 8543313Sbrian CheckerContext &C) const; 8643313Sbrian 8743313Sbrian /// Deal with symbol escape as a byproduct of a region change. 8843313Sbrian ProgramStateRef 8943313Sbrian checkRegionChanges(ProgramStateRef state, 9043313Sbrian const StoreManager::InvalidatedSymbols *invalidated, 9185357Speter ArrayRef<const MemRegion *> ExplicitRegions, 9249472Sbrian ArrayRef<const MemRegion *> Regions, 9349472Sbrian const CallEvent *Call) const; 9449472Sbrian bool wantsRegionChangeUpdate(ProgramStateRef state) const { 9549472Sbrian return true; 9649472Sbrian } 9752942Sbrian}; 9852942Sbrian 9952942Sbrian} // end anonymous namespace 10052942Sbrian 10152942Sbrian/// The state of the checker is a map from tracked stream symbols to their 10252942Sbrian/// state. Let's store it in the ProgramState. 10393418SbrianREGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) 10493418Sbrian 10593418Sbriannamespace { 10652942Sbrianclass StopTrackingCallback : public SymbolVisitor { 10732589Sbrian ProgramStateRef state; 10832589Sbrianpublic: 109121702Sru StopTrackingCallback(ProgramStateRef st) : state(st) {} 110121702Sru ProgramStateRef getState() const { return state; } 111121702Sru 112121702Sru bool VisitSymbol(SymbolRef sym) { 113121702Sru state = state->remove<StreamMap>(sym); 114121702Sru return true; 115121702Sru } 1166059Samurai}; 117} // end anonymous namespace 118 119 120SimpleStreamChecker::SimpleStreamChecker() : IIfopen(0), IIfclose(0) { 121 // Initialize the bug types. 122 DoubleCloseBugType.reset(new BugType("Double fclose", 123 "Unix Stream API Error")); 124 125 LeakBugType.reset(new BugType("Resource Leak", 126 "Unix Stream API Error")); 127 // Sinks are higher importance bugs as well as calls to assert() or exit(0). 128 LeakBugType->setSuppressOnSink(true); 129} 130 131void SimpleStreamChecker::checkPostCall(const CallEvent &Call, 132 CheckerContext &C) const { 133 initIdentifierInfo(C.getASTContext()); 134 135 if (!Call.isGlobalCFunction()) 136 return; 137 138 if (Call.getCalleeIdentifier() != IIfopen) 139 return; 140 141 // Get the symbolic value corresponding to the file handle. 142 SymbolRef FileDesc = Call.getReturnValue().getAsSymbol(); 143 if (!FileDesc) 144 return; 145 146 // Generate the next transition (an edge in the exploded graph). 147 ProgramStateRef State = C.getState(); 148 State = State->set<StreamMap>(FileDesc, StreamState::getOpened()); 149 C.addTransition(State); 150} 151 152void SimpleStreamChecker::checkPreCall(const CallEvent &Call, 153 CheckerContext &C) const { 154 initIdentifierInfo(C.getASTContext()); 155 156 if (!Call.isGlobalCFunction()) 157 return; 158 159 if (Call.getCalleeIdentifier() != IIfclose) 160 return; 161 162 if (Call.getNumArgs() != 1) 163 return; 164 165 // Get the symbolic value corresponding to the file handle. 166 SymbolRef FileDesc = Call.getArgSVal(0).getAsSymbol(); 167 if (!FileDesc) 168 return; 169 170 // Check if the stream has already been closed. 171 ProgramStateRef State = C.getState(); 172 const StreamState *SS = State->get<StreamMap>(FileDesc); 173 if (SS && SS->isClosed()) { 174 reportDoubleClose(FileDesc, Call, C); 175 return; 176 } 177 178 // Generate the next transition, in which the stream is closed. 179 State = State->set<StreamMap>(FileDesc, StreamState::getClosed()); 180 C.addTransition(State); 181} 182 183static bool isLeaked(SymbolRef Sym, const StreamState &SS, 184 bool IsSymDead, ProgramStateRef State) { 185 if (IsSymDead && SS.isOpened()) { 186 // If a symbol is NULL, assume that fopen failed on this path. 187 // A symbol should only be considered leaked if it is non-null. 188 ConstraintManager &CMgr = State->getConstraintManager(); 189 ConditionTruthVal OpenFailed = CMgr.isNull(State, Sym); 190 return !OpenFailed.isConstrainedTrue(); 191 } 192 return false; 193} 194 195void SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, 196 CheckerContext &C) const { 197 ProgramStateRef State = C.getState(); 198 SymbolVector LeakedStreams; 199 StreamMapTy TrackedStreams = State->get<StreamMap>(); 200 for (StreamMapTy::iterator I = TrackedStreams.begin(), 201 E = TrackedStreams.end(); I != E; ++I) { 202 SymbolRef Sym = I->first; 203 bool IsSymDead = SymReaper.isDead(Sym); 204 205 // Collect leaked symbols. 206 if (isLeaked(Sym, I->second, IsSymDead, State)) 207 LeakedStreams.push_back(Sym); 208 209 // Remove the dead symbol from the streams map. 210 if (IsSymDead) 211 State = State->remove<StreamMap>(Sym); 212 } 213 214 ExplodedNode *N = C.addTransition(State); 215 reportLeaks(LeakedStreams, C, N); 216} 217 218void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym, 219 const CallEvent &Call, 220 CheckerContext &C) const { 221 // We reached a bug, stop exploring the path here by generating a sink. 222 ExplodedNode *ErrNode = C.generateSink(); 223 // If we've already reached this node on another path, return. 224 if (!ErrNode) 225 return; 226 227 // Generate the report. 228 BugReport *R = new BugReport(*DoubleCloseBugType, 229 "Closing a previously closed file stream", ErrNode); 230 R->addRange(Call.getSourceRange()); 231 R->markInteresting(FileDescSym); 232 C.emitReport(R); 233} 234 235void SimpleStreamChecker::reportLeaks(SymbolVector LeakedStreams, 236 CheckerContext &C, 237 ExplodedNode *ErrNode) const { 238 // Attach bug reports to the leak node. 239 // TODO: Identify the leaked file descriptor. 240 for (llvm::SmallVector<SymbolRef, 2>::iterator 241 I = LeakedStreams.begin(), E = LeakedStreams.end(); I != E; ++I) { 242 BugReport *R = new BugReport(*LeakBugType, 243 "Opened file is never closed; potential resource leak", ErrNode); 244 R->markInteresting(*I); 245 C.emitReport(R); 246 } 247} 248 249// Check various ways a symbol can be invalidated. 250// Stop tracking symbols when a value escapes as a result of checkBind. 251// A value escapes in three possible cases: 252// (1) We are binding to something that is not a memory region. 253// (2) We are binding to a MemRegion that does not have stack storage 254// (3) We are binding to a MemRegion with stack storage that the store 255// does not understand. 256void SimpleStreamChecker::checkBind(SVal loc, SVal val, const Stmt *S, 257 CheckerContext &C) const { 258 // Are we storing to something that causes the value to "escape"? 259 bool escapes = true; 260 ProgramStateRef state = C.getState(); 261 262 if (loc::MemRegionVal *regionLoc = dyn_cast<loc::MemRegionVal>(&loc)) { 263 escapes = !regionLoc->getRegion()->hasStackStorage(); 264 265 if (!escapes) { 266 // To test (3), generate a new state with the binding added. If it is 267 // the same state, then it escapes (since the store cannot represent 268 // the binding). Do this only if we know that the store is not supposed 269 // to generate the same state. 270 SVal StoredVal = state->getSVal(regionLoc->getRegion()); 271 if (StoredVal != val) 272 escapes = (state == (state->bindLoc(*regionLoc, val))); 273 } 274 } 275 276 // If our store can represent the binding and we aren't storing to something 277 // that doesn't have local storage then just return the state and 278 // continue as is. 279 if (!escapes) 280 return; 281 282 // Otherwise, find all symbols referenced by 'val' that we are tracking 283 // and stop tracking them. 284 state = state->scanReachableSymbols<StopTrackingCallback>(val).getState(); 285 C.addTransition(state); 286} 287 288bool SimpleStreamChecker::guaranteedNotToCloseFile(const CallEvent &Call) const{ 289 // If it's not in a system header, assume it might close a file. 290 if (!Call.isInSystemHeader()) 291 return false; 292 293 // Handle cases where we know a buffer's /address/ can escape. 294 if (Call.argumentsMayEscape()) 295 return false; 296 297 // Note, even though fclose closes the file, we do not list it here 298 // since the checker is modeling the call. 299 300 return true; 301} 302 303// If the symbol we are tracking is invalidated, do not track the symbol as 304// we cannot reason about it anymore. 305ProgramStateRef 306SimpleStreamChecker::checkRegionChanges(ProgramStateRef State, 307 const StoreManager::InvalidatedSymbols *invalidated, 308 ArrayRef<const MemRegion *> ExplicitRegions, 309 ArrayRef<const MemRegion *> Regions, 310 const CallEvent *Call) const { 311 312 if (!invalidated || invalidated->empty()) 313 return State; 314 315 // If it's a call which might close the file, we assume that all regions 316 // (explicit and implicit) escaped. Otherwise, whitelist explicit pointers 317 // (the parameters to the call); we still can track them. 318 llvm::SmallPtrSet<SymbolRef, 8> WhitelistedSymbols; 319 if (!Call || guaranteedNotToCloseFile(*Call)) { 320 for (ArrayRef<const MemRegion *>::iterator I = ExplicitRegions.begin(), 321 E = ExplicitRegions.end(); I != E; ++I) { 322 if (const SymbolicRegion *R = (*I)->StripCasts()->getAs<SymbolicRegion>()) 323 WhitelistedSymbols.insert(R->getSymbol()); 324 } 325 } 326 327 for (StoreManager::InvalidatedSymbols::const_iterator I=invalidated->begin(), 328 E = invalidated->end(); I!=E; ++I) { 329 SymbolRef sym = *I; 330 if (WhitelistedSymbols.count(sym)) 331 continue; 332 // The symbol escaped. Optimistically, assume that the corresponding file 333 // handle will be closed somewhere else. 334 State = State->remove<StreamMap>(sym); 335 } 336 return State; 337} 338 339void SimpleStreamChecker::initIdentifierInfo(ASTContext &Ctx) const { 340 if (IIfopen) 341 return; 342 IIfopen = &Ctx.Idents.get("fopen"); 343 IIfclose = &Ctx.Idents.get("fclose"); 344} 345 346void ento::registerSimpleStreamChecker(CheckerManager &mgr) { 347 mgr.registerChecker<SimpleStreamChecker>(); 348} 349