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