SimpleStreamChecker.cpp revision 55fc873017f10f6f566b182b70f6fc22aefa3464
1//===-- SimpleStreamChecker.cpp -----------------------------------------*- C++ -*--// 2// 3// The LLVM Compiler Infrastructure 4// 5// This file is distributed under the University of Illinois Open Source 6// License. See LICENSE.TXT for details. 7// 8//===----------------------------------------------------------------------===// 9// 10// Defines a checker for proper use of fopen/fclose APIs. 11// - If a file has been closed with fclose, it should not be accessed again. 12// Accessing a closed file results in undefined behavior. 13// - If a file was opened with fopen, it must be closed with fclose before 14// the execution ends. Failing to do so results in a resource leak. 15// 16//===----------------------------------------------------------------------===// 17 18#include "ClangSACheckers.h" 19#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 20#include "clang/StaticAnalyzer/Core/Checker.h" 21#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" 22#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 23 24using namespace clang; 25using namespace ento; 26 27namespace { 28typedef llvm::SmallVector<SymbolRef, 2> SymbolVector; 29 30struct StreamState { 31private: 32 enum Kind { Opened, Closed } K; 33 StreamState(Kind InK) : K(InK) { } 34 35public: 36 bool isOpened() const { return K == Opened; } 37 bool isClosed() const { return K == Closed; } 38 39 static StreamState getOpened() { return StreamState(Opened); } 40 static StreamState getClosed() { return StreamState(Closed); } 41 42 bool operator==(const StreamState &X) const { 43 return K == X.K; 44 } 45 void Profile(llvm::FoldingSetNodeID &ID) const { 46 ID.AddInteger(K); 47 } 48}; 49 50class SimpleStreamChecker : public Checker<check::PostCall, 51 check::PreCall, 52 check::DeadSymbols, 53 check::Bind, 54 check::RegionChanges> { 55 56 mutable IdentifierInfo *IIfopen, *IIfclose; 57 58 OwningPtr<BugType> DoubleCloseBugType; 59 OwningPtr<BugType> LeakBugType; 60 61 void initIdentifierInfo(ASTContext &Ctx) const; 62 63 void reportDoubleClose(SymbolRef FileDescSym, 64 const CallEvent &Call, 65 CheckerContext &C) const; 66 67 void reportLeaks(SymbolVector LeakedStreams, 68 CheckerContext &C, 69 ExplodedNode *ErrNode) const; 70 71 bool guaranteedNotToCloseFile(const CallEvent &Call) const; 72 73public: 74 SimpleStreamChecker(); 75 76 /// Process fopen. 77 void checkPostCall(const CallEvent &Call, CheckerContext &C) const; 78 /// Process fclose. 79 void checkPreCall(const CallEvent &Call, CheckerContext &C) const; 80 81 void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; 82 83 /// Deal with symbol escape as a byproduct of a bind. 84 void checkBind(SVal location, SVal val, const Stmt*S, 85 CheckerContext &C) const; 86 87 /// Deal with symbol escape as a byproduct of a region change. 88 ProgramStateRef 89 checkRegionChanges(ProgramStateRef state, 90 const StoreManager::InvalidatedSymbols *invalidated, 91 ArrayRef<const MemRegion *> ExplicitRegions, 92 ArrayRef<const MemRegion *> Regions, 93 const CallEvent *Call) const; 94 bool wantsRegionChangeUpdate(ProgramStateRef state) const { 95 return true; 96 } 97}; 98 99} // end anonymous namespace 100 101/// The state of the checker is a map from tracked stream symbols to their 102/// state. Let's store it in the ProgramState. 103REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) 104 105namespace { 106class StopTrackingCallback : public SymbolVisitor { 107 ProgramStateRef state; 108public: 109 StopTrackingCallback(ProgramStateRef st) : state(st) {} 110 ProgramStateRef getState() const { return state; } 111 112 bool VisitSymbol(SymbolRef sym) { 113 state = state->remove<StreamMap>(sym); 114 return true; 115 } 116}; 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