SimpleStreamChecker.cpp revision 0c396d618dfa7cdd6ddafea24df7f74789d1f829
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/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 54 mutable IdentifierInfo *IIfopen, *IIfclose; 55 56 OwningPtr<BugType> DoubleCloseBugType; 57 OwningPtr<BugType> LeakBugType; 58 59 void initIdentifierInfo(ASTContext &Ctx) const; 60 61 void reportDoubleClose(SymbolRef FileDescSym, 62 const CallEvent &Call, 63 CheckerContext &C) const; 64 65 void reportLeaks(SymbolVector LeakedStreams, 66 CheckerContext &C, 67 ExplodedNode *ErrNode) const; 68 69public: 70 SimpleStreamChecker(); 71 72 /// Process fopen. 73 void checkPostCall(const CallEvent &Call, CheckerContext &C) const; 74 /// Process fclose. 75 void checkPreCall(const CallEvent &Call, CheckerContext &C) const; 76 77 void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; 78}; 79 80} // end anonymous namespace 81 82/// The state of the checker is a map from tracked stream symbols to their 83/// state. Let's store it in the ProgramState. 84REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) 85 86SimpleStreamChecker::SimpleStreamChecker() : IIfopen(0), IIfclose(0) { 87 // Initialize the bug types. 88 DoubleCloseBugType.reset(new BugType("Double fclose", 89 "Unix Stream API Error")); 90 91 LeakBugType.reset(new BugType("Resource Leak", 92 "Unix Stream API Error")); 93 // Sinks are higher importance bugs as well as calls to assert() or exit(0). 94 LeakBugType->setSuppressOnSink(true); 95} 96 97void SimpleStreamChecker::checkPostCall(const CallEvent &Call, 98 CheckerContext &C) const { 99 initIdentifierInfo(C.getASTContext()); 100 101 if (!Call.isGlobalCFunction()) 102 return; 103 104 if (Call.getCalleeIdentifier() != IIfopen) 105 return; 106 107 // Get the symbolic value corresponding to the file handle. 108 SymbolRef FileDesc = Call.getReturnValue().getAsSymbol(); 109 if (!FileDesc) 110 return; 111 112 // Generate the next transition (an edge in the exploded graph). 113 ProgramStateRef State = C.getState(); 114 State = State->set<StreamMap>(FileDesc, StreamState::getOpened()); 115 C.addTransition(State); 116} 117 118void SimpleStreamChecker::checkPreCall(const CallEvent &Call, 119 CheckerContext &C) const { 120 initIdentifierInfo(C.getASTContext()); 121 122 if (!Call.isGlobalCFunction()) 123 return; 124 125 if (Call.getCalleeIdentifier() != IIfclose) 126 return; 127 128 if (Call.getNumArgs() != 1) 129 return; 130 131 // Get the symbolic value corresponding to the file handle. 132 SymbolRef FileDesc = Call.getArgSVal(0).getAsSymbol(); 133 if (!FileDesc) 134 return; 135 136 // Check if the stream has already been closed. 137 ProgramStateRef State = C.getState(); 138 const StreamState *SS = State->get<StreamMap>(FileDesc); 139 if (SS && SS->isClosed()) { 140 reportDoubleClose(FileDesc, Call, C); 141 return; 142 } 143 144 // Generate the next transition, in which the stream is closed. 145 State = State->set<StreamMap>(FileDesc, StreamState::getClosed()); 146 C.addTransition(State); 147} 148 149static bool isLeaked(SymbolRef Sym, const StreamState &SS, 150 bool IsSymDead, ProgramStateRef State) { 151 if (IsSymDead && SS.isOpened()) { 152 // If a symbol is NULL, assume that fopen failed on this path. 153 // A symbol should only be considered leaked if it is non-null. 154 ConstraintManager &CMgr = State->getConstraintManager(); 155 ConditionTruthVal OpenFailed = CMgr.isNull(State, Sym); 156 return !OpenFailed.isConstrainedTrue(); 157 } 158 return false; 159} 160 161void SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, 162 CheckerContext &C) const { 163 ProgramStateRef State = C.getState(); 164 SymbolVector LeakedStreams; 165 StreamMapTy TrackedStreams = State->get<StreamMap>(); 166 for (StreamMapTy::iterator I = TrackedStreams.begin(), 167 E = TrackedStreams.end(); I != E; ++I) { 168 SymbolRef Sym = I->first; 169 bool IsSymDead = SymReaper.isDead(Sym); 170 171 // Collect leaked symbols. 172 if (isLeaked(Sym, I->second, IsSymDead, State)) 173 LeakedStreams.push_back(Sym); 174 175 // Remove the dead symbol from the streams map. 176 if (IsSymDead) 177 State = State->remove<StreamMap>(Sym); 178 } 179 180 ExplodedNode *N = C.addTransition(State); 181 reportLeaks(LeakedStreams, C, N); 182} 183 184void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym, 185 const CallEvent &Call, 186 CheckerContext &C) const { 187 // We reached a bug, stop exploring the path here by generating a sink. 188 ExplodedNode *ErrNode = C.generateSink(); 189 // If we've already reached this node on another path, return. 190 if (!ErrNode) 191 return; 192 193 // Generate the report. 194 BugReport *R = new BugReport(*DoubleCloseBugType, 195 "Closing a previously closed file stream", ErrNode); 196 R->addRange(Call.getSourceRange()); 197 R->markInteresting(FileDescSym); 198 C.emitReport(R); 199} 200 201void SimpleStreamChecker::reportLeaks(SymbolVector LeakedStreams, 202 CheckerContext &C, 203 ExplodedNode *ErrNode) const { 204 // Attach bug reports to the leak node. 205 // TODO: Identify the leaked file descriptor. 206 for (llvm::SmallVector<SymbolRef, 2>::iterator 207 I = LeakedStreams.begin(), E = LeakedStreams.end(); I != E; ++I) { 208 BugReport *R = new BugReport(*LeakBugType, 209 "Opened file is never closed; potential resource leak", ErrNode); 210 R->markInteresting(*I); 211 C.emitReport(R); 212 } 213} 214 215void SimpleStreamChecker::initIdentifierInfo(ASTContext &Ctx) const { 216 if (IIfopen) 217 return; 218 IIfopen = &Ctx.Idents.get("fopen"); 219 IIfclose = &Ctx.Idents.get("fclose"); 220} 221 222void ento::registerSimpleStreamChecker(CheckerManager &mgr) { 223 mgr.registerChecker<SimpleStreamChecker>(); 224} 225