SimpleStreamChecker.cpp revision bbb751a1788c461bc9765ec3387536cad6b52619
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 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 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 139void SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, 140 CheckerContext &C) const { 141 ProgramStateRef State = C.getState(); 142 StreamMapTy TrackedStreams = State->get<StreamMap>(); 143 SymbolVector LeakedStreams; 144 for (StreamMapTy::iterator I = TrackedStreams.begin(), 145 E = TrackedStreams.end(); I != E; ++I) { 146 SymbolRef Sym = I->first; 147 if (SymReaper.isDead(Sym)) { 148 const StreamState &SS = I->second; 149 if (SS.isOpened()) { 150 // If a symbolic region is NULL, assume that allocation failed on 151 // this path and do not report a leak. 152 if (!State->getConstraintManager().isNull(State, Sym).isTrue()) 153 LeakedStreams.push_back(Sym); 154 } 155 156 // Remove the dead symbol from the streams map. 157 State = State->remove<StreamMap>(Sym); 158 } 159 } 160 161 ExplodedNode *N = C.addTransition(State); 162 reportLeaks(LeakedStreams, C, N); 163} 164 165void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym, 166 const CallExpr *CallExpr, 167 CheckerContext &C) const { 168 // We reached a bug, stop exploring the path here by generating a sink. 169 ExplodedNode *ErrNode = C.generateSink(); 170 // If we've already reached this node on another path, return. 171 if (!ErrNode) 172 return; 173 174 // Generate the report. 175 BugReport *R = new BugReport(*DoubleCloseBugType, 176 "Closing a previously closed file stream", ErrNode); 177 R->addRange(CallExpr->getSourceRange()); 178 R->markInteresting(FileDescSym); 179 C.EmitReport(R); 180} 181 182void SimpleStreamChecker::reportLeaks(SymbolVector LeakedStreams, 183 CheckerContext &C, 184 ExplodedNode *ErrNode) const { 185 // Attach bug reports to the leak node. 186 // TODO: Identify the leaked file descriptor. 187 for (llvm::SmallVector<SymbolRef, 2>::iterator 188 I = LeakedStreams.begin(), E = LeakedStreams.end(); I != E; ++I) { 189 BugReport *R = new BugReport(*LeakBugType, 190 "Opened file is never closed; potential resource leak", ErrNode); 191 R->markInteresting(*I); 192 C.EmitReport(R); 193 } 194} 195 196void SimpleStreamChecker::initIdentifierInfo(ASTContext &Ctx) const { 197 if (IIfopen) 198 return; 199 IIfopen = &Ctx.Idents.get("fopen"); 200 IIfclose = &Ctx.Idents.get("fclose"); 201} 202 203void ento::registerSimpleStreamChecker(CheckerManager &mgr) { 204 mgr.registerChecker<SimpleStreamChecker>(); 205} 206