SimpleStreamChecker.cpp revision ac150f2619efcadbf23acd6e86695b5412723eb1
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/Checker.h" 20#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 21#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 22 23using namespace clang; 24using namespace ento; 25 26namespace { 27typedef llvm::SmallVector<SymbolRef, 2> SymbolVector; 28 29struct StreamState { 30 enum Kind { Opened, Closed } K; 31 32 StreamState(Kind InK) : K(InK) { } 33 34 bool isOpened() const { return K == Opened; } 35 bool isClosed() const { return K == Closed; } 36 37 static StreamState getOpened() { return StreamState(Opened); } 38 static StreamState getClosed() { return StreamState(Closed); } 39 40 bool operator==(const StreamState &X) const { 41 return K == X.K; 42 } 43 void Profile(llvm::FoldingSetNodeID &ID) const { 44 ID.AddInteger(K); 45 } 46}; 47 48class SimpleStreamChecker: public Checker<check::PostStmt<CallExpr>, 49 check::PreStmt<CallExpr>, 50 check::DeadSymbols, 51 eval::Assume > { 52 53 mutable IdentifierInfo *IIfopen, *IIfclose; 54 55 mutable OwningPtr<BugType> DoubleCloseBugType; 56 mutable OwningPtr<BugType> LeakBugType; 57 58 void initIdentifierInfo(ASTContext &Ctx) const; 59 60 void reportDoubleClose(SymbolRef FileDescSym, 61 const CallExpr *Call, 62 CheckerContext &C) const; 63 64 ExplodedNode *reportLeaks(SymbolVector LeakedStreams, 65 CheckerContext &C) const; 66 67public: 68 SimpleStreamChecker() : IIfopen(0), IIfclose(0) {} 69 70 /// Process fopen. 71 void checkPostStmt(const CallExpr *Call, CheckerContext &C) const; 72 /// Process fclose. 73 void checkPreStmt(const CallExpr *Call, CheckerContext &C) const; 74 75 void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; 76 ProgramStateRef evalAssume(ProgramStateRef state, SVal Cond, 77 bool Assumption) const; 78 79}; 80 81} // end anonymous namespace 82 83/// The state of the checker is a map from tracked stream symbols to their 84/// state. Let's store it in the ProgramState. 85REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) 86 87void SimpleStreamChecker::checkPostStmt(const CallExpr *Call, 88 CheckerContext &C) const { 89 initIdentifierInfo(C.getASTContext()); 90 91 if (C.getCalleeIdentifier(Call) != IIfopen) 92 return; 93 94 // Get the symbolic value corresponding to the file handle. 95 SymbolRef FileDesc = C.getSVal(Call).getAsSymbol(); 96 if (!FileDesc) 97 return; 98 99 // Generate the next transition (an edge in the exploded graph). 100 ProgramStateRef State = C.getState(); 101 State = State->set<StreamMap>(FileDesc, StreamState::getOpened()); 102 C.addTransition(State); 103} 104 105void SimpleStreamChecker::checkPreStmt(const CallExpr *Call, 106 CheckerContext &C) const { 107 initIdentifierInfo(C.getASTContext()); 108 109 if (C.getCalleeIdentifier(Call) != IIfclose) 110 return; 111 if (Call->getNumArgs() != 1) 112 return; 113 114 // Get the symbolic value corresponding to the file handle. 115 SymbolRef FileDesc = C.getSVal(Call->getArg(0)).getAsSymbol(); 116 if (!FileDesc) 117 return; 118 119 // Check if the stream has already been closed. 120 ProgramStateRef State = C.getState(); 121 const StreamState *SS = State->get<StreamMap>(FileDesc); 122 if (SS && SS->isClosed()) 123 reportDoubleClose(FileDesc, Call, C); 124 125 // Generate the next transition, in which the stream is closed. 126 State = State->set<StreamMap>(FileDesc, StreamState::getClosed()); 127 C.addTransition(State); 128} 129 130void SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, 131 CheckerContext &C) const { 132 ProgramStateRef State = C.getState(); 133 StreamMap TrackedStreams = State->get<StreamMap>(); 134 SymbolVector LeakedStreams; 135 for (StreamMap::iterator I = TrackedStreams.begin(), 136 E = TrackedStreams.end(); I != E; ++I) { 137 SymbolRef Sym = I->first; 138 if (SymReaper.isDead(Sym)) { 139 const StreamState &SS = I->second; 140 if (SS.isOpened()) 141 LeakedStreams.push_back(Sym); 142 143 // Remove the dead symbol from the streams map. 144 State = State->remove<StreamMap>(Sym); 145 } 146 } 147 148 ExplodedNode *N = reportLeaks(LeakedStreams, C); 149 C.addTransition(State, N); 150} 151 152// If a symbolic region is assumed to NULL (or another constant), stop tracking 153// it - assuming that allocation failed on this path. 154ProgramStateRef SimpleStreamChecker::evalAssume(ProgramStateRef State, 155 SVal Cond, 156 bool Assumption) const { 157 StreamMap TrackedStreams = State->get<StreamMap>(); 158 SymbolVector LeakedStreams; 159 for (StreamMap::iterator I = TrackedStreams.begin(), 160 E = TrackedStreams.end(); I != E; ++I) { 161 SymbolRef Sym = I->first; 162 if (State->getConstraintManager().isNull(State, Sym).isTrue()) 163 State = State->remove<StreamMap>(Sym); 164 } 165 return State; 166} 167 168void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym, 169 const CallExpr *CallExpr, 170 CheckerContext &C) const { 171 // We reached a bug, stop exploring the path here by generating a sink. 172 ExplodedNode *ErrNode = C.generateSink(); 173 // If this error node already exists, return. 174 if (!ErrNode) 175 return; 176 177 // Initialize the bug type. 178 if (!DoubleCloseBugType) 179 DoubleCloseBugType.reset(new BugType("Double fclose", 180 "Unix Stream API Error")); 181 // Generate the report. 182 BugReport *R = new BugReport(*DoubleCloseBugType, 183 "Closing a previously closed file stream", ErrNode); 184 R->addRange(CallExpr->getSourceRange()); 185 R->markInteresting(FileDescSym); 186 C.EmitReport(R); 187} 188 189ExplodedNode *SimpleStreamChecker::reportLeaks(SymbolVector LeakedStreams, 190 CheckerContext &C) const { 191 ExplodedNode *Pred = C.getPredecessor(); 192 if (LeakedStreams.empty()) 193 return Pred; 194 195 // Generate an intermediate node representing the leak point. 196 static SimpleProgramPointTag Tag("StreamChecker : Leak"); 197 ExplodedNode *ErrNode = C.addTransition(Pred->getState(), Pred, &Tag); 198 if (!ErrNode) 199 return Pred; 200 201 // Initialize the bug type. 202 if (!LeakBugType) { 203 LeakBugType.reset(new BuiltinBug("Resource Leak", 204 "Unix Stream API Error")); 205 // Sinks are higher importance bugs as well as calls to assert() or exit(0). 206 LeakBugType->setSuppressOnSink(true); 207 } 208 209 // Attach bug reports to the leak node. 210 for (llvm::SmallVector<SymbolRef, 2>::iterator 211 I = LeakedStreams.begin(), E = LeakedStreams.end(); I != E; ++I) { 212 BugReport *R = new BugReport(*LeakBugType, 213 "Opened file is never closed; potential resource leak", ErrNode); 214 C.EmitReport(R); 215 } 216 217 return ErrNode; 218} 219 220void SimpleStreamChecker::initIdentifierInfo(ASTContext &Ctx) const { 221 if (IIfopen) 222 return; 223 IIfopen = &Ctx.Idents.get("fopen"); 224 IIfclose = &Ctx.Idents.get("fclose"); 225} 226 227void ento::registerSimpleStreamChecker(CheckerManager &mgr) { 228 mgr.registerChecker<SimpleStreamChecker>(); 229} 230